Class: Tidewave

Inherits:
Object
  • Object
show all
Defined in:
lib/tidewave.rb,
lib/tidewave.rb,
lib/tidewave/tool.rb,
lib/tidewave/railtie.rb,
lib/tidewave/version.rb,
lib/tidewave/magic_bytes.rb,
lib/tidewave/configuration.rb,
lib/tidewave/browser_control.rb,
lib/tidewave/database_adapter.rb,
lib/tidewave/database_adapters/sequel.rb,
lib/tidewave/database_adapters/active_record.rb

Defined Under Namespace

Modules: DatabaseAdapters, MagicBytes, Tools Classes: BrowserControl, Configuration, DatabaseAdapter, ExceptionsMiddleware, QuietRequestsMiddleware, Railtie, Tool, ToolbarBody

Constant Summary collapse

TIDEWAVE_ROUTE =
"tidewave".freeze
MCP_ROUTE =
"mcp".freeze
CONFIG_ROUTE =
"config".freeze
CONNECT_ROUTE =
"connect".freeze
UPLOAD_ROUTE =
"upload".freeze
WS_ROUTE =
"ws".freeze
PROTOCOL_VERSION =
"2025-03-26".freeze
MAX_UPLOAD_SIZE =
10_000_000
ALLOWED_UPLOAD_CONTENT_TYPES =
[ "image/png", "image/jpeg", "video/webm" ].freeze
ALLOWED_UPLOAD_TYPES =
[ "screenshot", "recording" ].freeze
TMP_DIR =
"tmp".freeze
INVALID_IP =
<<~TEXT.freeze
  For security reasons, Tidewave does not accept remote connections by default.

  If you really want to allow remote connections, configure Tidewave with the `allow_remote_access: true` option
TEXT
INVALID_FETCH_SITE =
"For security reasons, Tidewave only accepts requests from the same origin your web app is running on.".freeze
INVALID_ORIGIN =
"For security reasons, Tidewave does not accept requests with an origin header for this endpoint.".freeze
INVALID_UPLOAD =
"Bad Request: missing or invalid file parameter".freeze
ENCODED_HTML_WARNING =
<<~TEXT.freeze
  Tidewave could not inject the toolbar because the HTML response is encoded.

  If you use Rack::Deflater or another compression middleware, place it before Tidewave in the middleware stack.
TEXT
DEFAULT_OPTIONS =
{
  allow_remote_access: false,
  browser_control: nil,
  client_url: "https://tidewave.ai",
  framework_type: "rack",
  team: {},
  toolbar: true
}.freeze
VERSION =
"0.8.1"

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ Tidewave

Returns a new instance of Tidewave.

Raises:

  • (ArgumentError)


108
109
110
111
112
113
114
115
116
117
# File 'lib/tidewave.rb', line 108

def initialize(app, options = {})
  @app = app
  @options = DEFAULT_OPTIONS.merge(options || {})
  raise ArgumentError, "project_name is required" if @options[:project_name].to_s.empty?

  @logger = @options[:logger]
  @root = @options[:root] ? Pathname.new(@options[:root].to_s) : Pathname.pwd
  @browser_control = @options[:browser_control]
  @tools = build_tool_registry
end

Instance Method Details

#call(env) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/tidewave.rb', line 119

def call(env)
  request = Rack::Request.new(env)
  path = request.path.split("/").reject(&:empty?)

  if path[0] == TIDEWAVE_ROUTE
    return forbidden(INVALID_IP) unless valid_client_ip?(request)

    origin_error = check_origin(request, path)
    return origin_error if origin_error

    case [ request.request_method, path ]
    when [ "GET", [ TIDEWAVE_ROUTE ] ]
      home_endpoint(request)
    when [ "GET", [ TIDEWAVE_ROUTE, WS_ROUTE ] ]
      unless @browser_control
        raise "this route is currently only supported for Rails"
      end

      @browser_control.call(request.env)
    when [ "GET", [ TIDEWAVE_ROUTE, CONNECT_ROUTE ] ]
      app_endpoint(request)
    when [ "GET", [ TIDEWAVE_ROUTE, CONFIG_ROUTE ] ]
      config_endpoint(request)
    when [ "POST", [ TIDEWAVE_ROUTE, MCP_ROUTE ] ]
      mcp_endpoint(request)
    when [ "POST", [ TIDEWAVE_ROUTE, UPLOAD_ROUTE ] ]
      upload_endpoint(request)
    else
      # The MCP Streamable HTTP transport requires the MCP endpoint to answer
      # non-POST methods with 405 (GET without SSE support, DELETE, etc.)
      path == [ TIDEWAVE_ROUTE, MCP_ROUTE ] ? method_not_allowed() : not_found()
    end
  else
    inject_toolbar(request, strip_x_frame_options(@app.call(env)))
  end
end