# frozen_string_literal: true require "webrick" require "uri" module Dispatch module Adapter class Claude < Base module OAuth class CallbackServer SUCCESS_HTML = <<~HTML Authentication Successful

Authentication Successful

You can close this tab and return to your terminal.

HTML def initialize(port: 54_545, timeout: 300) @port = port @timeout = timeout @queue = Queue.new @server = nil @thread = nil end def callback_url "http://localhost:#{@port}/callback" end def start logger = WEBrick::Log.new(File::NULL, WEBrick::Log::FATAL) @server = WEBrick::HTTPServer.new( BindAddress: "127.0.0.1", Port: @port, Logger: logger, AccessLog: [] ) @server.mount_proc("/callback") do |req, res| if req.request_method == "GET" code = req.query["code"] state = req.query["state"] if code res.status = 200 res.content_type = "text/html; charset=utf-8" res.body = SUCCESS_HTML @queue << [code, state] shutdown_async else res.status = 400 res.body = "Missing code parameter" end else res.status = 405 res.body = "Method Not Allowed" end end @server.mount_proc("/") do |_req, res| res.status = 404 res.body = "Not Found" end @thread = Thread.new do @server.start rescue StandardError # Server was shut down end # Start a timeout watchdog Thread.new do sleep(@timeout) @queue << AuthenticationError.new( "OAuth callback timed out after #{@timeout}s — no browser response received.", provider: ClaudeErrors::PROVIDER ) shutdown_async end end def await_code result = @queue.pop raise result if result.is_a?(Exception) result end def stop shutdown_async @thread&.join(5) rescue StandardError # Ignore errors during shutdown end private def shutdown_async return unless @server server = @server @server = nil Thread.new { server.shutdown rescue nil } # rubocop:disable Style/RescueModifier end end end end end end