lib/ferrum/cookies.rb



# frozen_string_literal: true

require "ferrum/cookies/cookie"

module Ferrum
  class Cookies
    include Enumerable

    def initialize(page)
      @page = page
    end

    #
    # Enumerates over all cookies.
    #
    # @yield [cookie]
    #   The given block will be passed each cookie.
    #
    # @yieldparam [Cookie] cookie
    #   A cookie in the browser.
    #
    # @return [Enumerator]
    #   If no block is given, an Enumerator object will be returned.
    #
    def each
      return enum_for(__method__) unless block_given?

      cookies = @page.command("Network.getAllCookies")["cookies"]

      cookies.each do |c|
        yield Cookie.new(c)
      end
    end

    #
    # Returns cookies hash.
    #
    # @return [Hash{String => Cookie}]
    #
    # @example
    #   browser.cookies.all # => {
    #   #  "NID" => #<Ferrum::Cookies::Cookie:0x0000558624b37a40 @attributes={
    #   #     "name"=>"NID", "value"=>"...", "domain"=>".google.com", "path"=>"/",
    #   #     "expires"=>1583211046.575681, "size"=>178, "httpOnly"=>true, "secure"=>false, "session"=>false
    #   #  }>
    #   # }
    #
    def all
      each.to_h do |cookie|
        [cookie.name, cookie]
      end
    end

    #
    # Returns cookie.
    #
    # @param [String] name
    #   The cookie name to fetch.
    #
    # @return [Cookie, nil]
    #   The cookie with the matching name.
    #
    # @example
    #   browser.cookies["NID"] # =>
    #   # <Ferrum::Cookies::Cookie:0x0000558624b67a88 @attributes={
    #   #  "name"=>"NID", "value"=>"...", "domain"=>".google.com",
    #   #  "path"=>"/", "expires"=>1583211046.575681, "size"=>178,
    #   #  "httpOnly"=>true, "secure"=>false, "session"=>false
    #   # }>
    #
    def [](name)
      find { |cookie| cookie.name == name }
    end

    #
    # Sets a cookie.
    #
    # @param [Hash{Symbol => Object}, Cookie] options
    #
    # @option options [String] :name
    #   The cookie param name.
    #
    # @option options [String] :value
    #   The cookie param value.
    #
    # @option options [String] :domain
    #   The domain the cookie belongs to.
    #
    # @option options [String] :path
    #   The path that the cookie is bound to.
    #
    # @option options [Integer] :expires
    #   When the cookie will expire.
    #
    # @option options [Integer] :size
    #   The size of the cookie.
    #
    # @option options [Boolean] :httponly
    #   Specifies whether the cookie `HttpOnly`.
    #
    # @option options [Boolean] :secure
    #   Specifies whether the cookie is marked as `Secure`.
    #
    # @option options [String] :samesite
    #   Specifies whether the cookie is `SameSite`.
    #
    # @option options [Boolean] :session
    #   Specifies whether the cookie is a session cookie.
    #
    # @example
    #   browser.cookies.set(name: "stealth", value: "omg", domain: "google.com") # => true
    #
    # @example
    #   nid_cookie = browser.cookies["NID"] # => <Ferrum::Cookies::Cookie:0x0000558624b67a88>
    #   browser.cookies.set(nid_cookie) # => true
    #
    def set(options)
      cookie = (
        options.is_a?(Cookie) ? options.attributes : options
      ).dup.transform_keys(&:to_sym)

      cookie[:domain] ||= default_domain

      cookie[:httpOnly] = cookie.delete(:httponly) if cookie.key?(:httponly)
      cookie[:sameSite] = cookie.delete(:samesite) if cookie.key?(:samesite)

      expires = cookie.delete(:expires).to_i
      cookie[:expires] = expires if expires.positive?

      @page.command("Network.setCookie", **cookie)["success"]
    end

    #
    # Removes given cookie.
    #
    # @param [String] name
    #
    # @param [Hash{Symbol => Object}] options
    #   Additional keyword arguments.
    #
    # @option options [String] :domain
    #
    # @option options [String] :url
    #
    # @example
    #   browser.cookies.remove(name: "stealth", domain: "google.com") # => true
    #
    def remove(name:, **options)
      raise "Specify :domain or :url option" if !options[:domain] && !options[:url] && !default_domain

      options = options.merge(name: name)
      options[:domain] ||= default_domain

      @page.command("Network.deleteCookies", **options)

      true
    end

    #
    # Removes all cookies for current page.
    #
    # @return [true]
    #
    # @example
    #   browser.cookies.clear # => true
    #
    def clear
      @page.command("Network.clearBrowserCookies")
      true
    end

    private

    def default_domain
      URI.parse(@page.base_url).host if @page.base_url
    end
  end
end