lib/ferrum/browser/options.rb



# frozen_string_literal: true

module Ferrum
  class Browser
    class Options
      BROWSER_PORT = "0"
      BROWSER_HOST = "127.0.0.1"
      WINDOW_SIZE = [1024, 768].freeze
      BASE_URL_SCHEMA = %w[http https].freeze
      DEFAULT_TIMEOUT = ENV.fetch("FERRUM_DEFAULT_TIMEOUT", 5).to_i
      PROCESS_TIMEOUT = ENV.fetch("FERRUM_PROCESS_TIMEOUT", 10).to_i
      DEBUG_MODE = !ENV.fetch("FERRUM_DEBUG", nil).nil?

      attr_reader :window_size, :logger, :ws_max_receive_size,
                  :js_errors, :base_url, :slowmo, :pending_connection_errors,
                  :url, :ws_url, :env, :process_timeout, :browser_name, :browser_path,
                  :save_path, :proxy, :port, :host, :headless, :browser_options,
                  :ignore_default_browser_options, :xvfb, :flatten
      attr_accessor :timeout, :default_user_agent

      def initialize(options = nil)
        @options = Hash(options&.dup)

        @port = @options.fetch(:port, BROWSER_PORT)
        @host = @options.fetch(:host, BROWSER_HOST)
        @timeout = @options.fetch(:timeout, DEFAULT_TIMEOUT)
        @window_size = @options.fetch(:window_size, WINDOW_SIZE)
        @js_errors = @options.fetch(:js_errors, false)
        @headless = @options.fetch(:headless, true)
        @flatten = @options.fetch(:flatten, true)
        @pending_connection_errors = @options.fetch(:pending_connection_errors, true)
        @process_timeout = @options.fetch(:process_timeout, PROCESS_TIMEOUT)
        @slowmo = @options[:slowmo].to_f

        @env = @options[:env]
        @xvfb = @options[:xvfb]
        @save_path = @options[:save_path]
        @browser_name = @options[:browser_name]
        @browser_path = @options[:browser_path]
        @ws_max_receive_size = @options[:ws_max_receive_size]
        @ignore_default_browser_options = @options[:ignore_default_browser_options]

        @proxy = validate_proxy(@options[:proxy])
        @logger = parse_logger(@options[:logger])
        @base_url = parse_base_url(@options[:base_url]) if @options[:base_url]
        @url = @options[:url].to_s if @options[:url]
        @ws_url = @options[:ws_url].to_s if @options[:ws_url]

        @options = @options.merge(window_size: @window_size).freeze
        @browser_options = @options.fetch(:browser_options, {}).freeze
      end

      def base_url=(value)
        @base_url = parse_base_url(value)
      end

      def extensions
        @extensions ||= Array(@options[:extensions]).map do |extension|
          (extension.is_a?(Hash) && extension[:source]) || File.read(extension)
        end
      end

      def validate_proxy(options)
        return unless options

        raise ArgumentError, "proxy options must be a Hash" unless options.is_a?(Hash)

        if options[:host].nil? && options[:port].nil?
          raise ArgumentError, "proxy options must be a Hash with at least :host | :port"
        end

        options
      end

      def to_h
        @options
      end

      private

      def parse_logger(logger)
        return logger if logger

        !logger && DEBUG_MODE ? $stdout.tap { |s| s.sync = true } : logger
      end

      def parse_base_url(value)
        parsed = Addressable::URI.parse(value)
        unless BASE_URL_SCHEMA.include?(parsed&.normalized_scheme)
          raise ArgumentError, "`base_url` should be absolute and include schema: #{BASE_URL_SCHEMA.join(' | ')}"
        end

        parsed
      end
    end
  end
end