Class: Webpipe::BrowserSession

Inherits:
Object
  • Object
show all
Defined in:
lib/webpipe/browser.rb

Overview

Handle to a persistent headless Chromium session on the server.

Server-side facts: sessions expire after 30 minutes idle; at most 2 concurrent running sessions per user. Always close when done.

Constant Summary collapse

TERMINAL_STATUSES =
%w[stopped error expired].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(http, id:, status:, url: nil, session_name: nil, expires_at: nil) ⇒ BrowserSession

Returns a new instance of BrowserSession.



34
35
36
37
38
39
40
41
# File 'lib/webpipe/browser.rb', line 34

def initialize(http, id:, status:, url: nil, session_name: nil, expires_at: nil)
  @http = http
  @id = id
  @status = status
  @url = url
  @session_name = session_name
  @expires_at = expires_at
end

Instance Attribute Details

#expires_atObject (readonly)

Returns the value of attribute expires_at.



30
31
32
# File 'lib/webpipe/browser.rb', line 30

def expires_at
  @expires_at
end

#idObject (readonly)

Returns the value of attribute id.



30
31
32
# File 'lib/webpipe/browser.rb', line 30

def id
  @id
end

#session_nameObject (readonly)

Returns the value of attribute session_name.



30
31
32
# File 'lib/webpipe/browser.rb', line 30

def session_name
  @session_name
end

#statusObject (readonly)

Returns the value of attribute status.



30
31
32
# File 'lib/webpipe/browser.rb', line 30

def status
  @status
end

#urlObject (readonly)

Returns the value of attribute url.



30
31
32
# File 'lib/webpipe/browser.rb', line 30

def url
  @url
end

Instance Method Details

#action(type, **params) ⇒ Object

Run a raw browser action. Returns the raw API payload Hash.



86
87
88
# File 'lib/webpipe/browser.rb', line 86

def action(type, **params)
  @http.post("#{base}/action", { type: type }.merge(params))
end

#check(selector) ⇒ Object



108
# File 'lib/webpipe/browser.rb', line 108

def check(selector) = action("check", selector:)

#clear_cookiesObject



116
# File 'lib/webpipe/browser.rb', line 116

def clear_cookies = @http.post("#{base}/cookies", { op: "clear" })

#click(selector, wait_after: nil) ⇒ Object



90
# File 'lib/webpipe/browser.rb', line 90

def click(selector, wait_after: nil) = action("click", **drop_nil(selector:, wait_after:))

#closeObject

Stop the session and release server resources.



67
68
69
70
71
# File 'lib/webpipe/browser.rb', line 67

def close
  @http.delete(base)
  @status = "stopped"
  nil
end

#evaluate(expression) ⇒ Object

Evaluate a JavaScript expression in the page (value in payload's "result").



106
# File 'lib/webpipe/browser.rb', line 106

def evaluate(expression) = action("evaluate", expression:)

#fill(selector, value) ⇒ Object



91
# File 'lib/webpipe/browser.rb', line 91

def fill(selector, value) = action("fill", selector:, value:)

#get_cookiesObject



110
111
112
113
# File 'lib/webpipe/browser.rb', line 110

def get_cookies
  data = @http.get("#{base}/cookies")
  data["cookies"] || data["data"] || []
end

#hover(selector) ⇒ Object



107
# File 'lib/webpipe/browser.rb', line 107

def hover(selector) = action("hover", selector:)

Navigate to a new URL (with anti-bot handling) and scrape it.



74
75
76
77
# File 'lib/webpipe/browser.rb', line 74

def navigate(url, formats: nil, scroll_to_bottom: nil, max_scrolls: nil, scroll_wait: nil, scroll_step: nil)
  body = { url: url }.merge(scrape_body(formats:, scroll_to_bottom:, max_scrolls:, scroll_wait:, scroll_step:))
  Document.from_hash(@http.post("#{base}/navigate", body)["data"])
end

#press(key, selector: nil, wait_after: nil) ⇒ Object



94
95
96
# File 'lib/webpipe/browser.rb', line 94

def press(key, selector: nil, wait_after: nil)
  action("press", **drop_nil(key:, selector:, wait_after:))
end

#refreshObject

Fetch the latest session state from the server.



44
45
46
47
48
49
50
51
# File 'lib/webpipe/browser.rb', line 44

def refresh
  info = @http.get(base)
  @status = info["status"]
  @url = info["url"]
  @session_name = info["session_name"]
  @expires_at = info["expires_at"]
  self
end

#save_sessionObject

Persist cookies/storage under this session's session_name.



119
# File 'lib/webpipe/browser.rb', line 119

def save_session = @http.post("#{base}/save_session")

#scrape(formats: nil, scroll_to_bottom: nil, max_scrolls: nil, scroll_wait: nil, scroll_step: nil) ⇒ Object

Scrape the current page without navigating (faster than navigate).



80
81
82
83
# File 'lib/webpipe/browser.rb', line 80

def scrape(formats: nil, scroll_to_bottom: nil, max_scrolls: nil, scroll_wait: nil, scroll_step: nil)
  body = scrape_body(formats:, scroll_to_bottom:, max_scrolls:, scroll_wait:, scroll_step:)
  Document.from_hash(@http.post("#{base}/scrape", body)["data"])
end

#screenshot(full_page: false) ⇒ Object

Take a screenshot. The API returns a base64 PNG inside the payload.



103
# File 'lib/webpipe/browser.rb', line 103

def screenshot(full_page: false) = action("screenshot", full_page:)

#scroll(direction: "down", amount: nil) ⇒ Object



98
# File 'lib/webpipe/browser.rb', line 98

def scroll(direction: "down", amount: nil) = action("scroll", **drop_nil(direction:, amount:))

#select(selector, value) ⇒ Object



92
# File 'lib/webpipe/browser.rb', line 92

def select(selector, value) = action("select", selector:, value:)

#set_cookies(cookies) ⇒ Object



115
# File 'lib/webpipe/browser.rb', line 115

def set_cookies(cookies) = @http.post("#{base}/cookies", { op: "set", cookies: cookies })

#wait(ms = 1000) ⇒ Object



99
# File 'lib/webpipe/browser.rb', line 99

def wait(ms = 1000) = action("wait", ms:)

#wait_for(selector, timeout: nil) ⇒ Object



100
# File 'lib/webpipe/browser.rb', line 100

def wait_for(selector, timeout: nil) = action("wait_for", **drop_nil(selector:, timeout:))

#wait_until_running(timeout: 60, poll_interval: 1) ⇒ Object

Block until the session is ready to accept commands.



54
55
56
57
58
59
60
61
62
63
64
# File 'lib/webpipe/browser.rb', line 54

def wait_until_running(timeout: 60, poll_interval: 1)
  deadline = Time.now + timeout
  loop do
    refresh
    return self if @status == "running"
    raise Error, "Browser session #{@id} entered terminal status '#{@status}'" if TERMINAL_STATUSES.include?(@status)
    raise PollTimeoutError, "Browser session #{@id} not running after #{timeout}s" if Time.now >= deadline

    sleep poll_interval
  end
end