ferrum 0.14 → 0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +309 -158
- data/lib/ferrum/browser/command.rb +4 -0
- data/lib/ferrum/browser/options/chrome.rb +8 -3
- data/lib/ferrum/browser/options.rb +38 -25
- data/lib/ferrum/browser/process.rb +42 -17
- data/lib/ferrum/browser.rb +38 -50
- data/lib/ferrum/client/subscriber.rb +76 -0
- data/lib/ferrum/client/web_socket.rb +126 -0
- data/lib/ferrum/client.rb +171 -0
- data/lib/ferrum/context.rb +19 -15
- data/lib/ferrum/contexts.rb +46 -12
- data/lib/ferrum/cookies.rb +28 -1
- data/lib/ferrum/downloads.rb +60 -0
- data/lib/ferrum/errors.rb +10 -3
- data/lib/ferrum/headers.rb +1 -1
- data/lib/ferrum/network/exchange.rb +10 -1
- data/lib/ferrum/network/intercepted_request.rb +5 -5
- data/lib/ferrum/network/request.rb +9 -0
- data/lib/ferrum/network.rb +36 -24
- data/lib/ferrum/node.rb +11 -0
- data/lib/ferrum/page/frames.rb +7 -9
- data/lib/ferrum/page/screenshot.rb +54 -28
- data/lib/ferrum/page.rb +192 -118
- data/lib/ferrum/proxy.rb +1 -1
- data/lib/ferrum/target.rb +25 -5
- data/lib/ferrum/utils/elapsed_time.rb +0 -2
- data/lib/ferrum/utils/event.rb +19 -0
- data/lib/ferrum/utils/platform.rb +4 -0
- data/lib/ferrum/utils/thread.rb +18 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum.rb +3 -0
- metadata +28 -17
- data/lib/ferrum/browser/client.rb +0 -103
- data/lib/ferrum/browser/subscriber.rb +0 -36
- data/lib/ferrum/browser/web_socket.rb +0 -91
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent-ruby"
|
4
|
+
require "forwardable"
|
5
|
+
require "ferrum/client/subscriber"
|
6
|
+
require "ferrum/client/web_socket"
|
7
|
+
require "ferrum/utils/thread"
|
8
|
+
|
9
|
+
module Ferrum
|
10
|
+
class SessionClient
|
11
|
+
attr_reader :client, :session_id
|
12
|
+
|
13
|
+
def self.event_name(event, session_id)
|
14
|
+
[event, session_id].compact.join("_")
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(client, session_id)
|
18
|
+
@client = client
|
19
|
+
@session_id = session_id
|
20
|
+
end
|
21
|
+
|
22
|
+
def command(method, async: false, **params)
|
23
|
+
message = build_message(method, params)
|
24
|
+
@client.send_message(message, async: async)
|
25
|
+
end
|
26
|
+
|
27
|
+
def on(event, &block)
|
28
|
+
@client.on(event_name(event), &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def subscribed?(event)
|
32
|
+
@client.subscribed?(event_name(event))
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_to_missing?(name, include_private)
|
36
|
+
@client.respond_to?(name, include_private)
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(name, *args, **opts, &block)
|
40
|
+
@client.send(name, *args, **opts, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
@client.subscriber.clear(session_id: session_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def build_message(method, params)
|
50
|
+
@client.build_message(method, params).merge(sessionId: session_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def event_name(event)
|
54
|
+
self.class.event_name(event, session_id)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Client
|
59
|
+
extend Forwardable
|
60
|
+
delegate %i[timeout timeout=] => :options
|
61
|
+
|
62
|
+
attr_reader :ws_url, :options, :subscriber
|
63
|
+
|
64
|
+
def initialize(ws_url, options)
|
65
|
+
@command_id = 0
|
66
|
+
@ws_url = ws_url
|
67
|
+
@options = options
|
68
|
+
@pendings = Concurrent::Hash.new
|
69
|
+
@ws = WebSocket.new(ws_url, options.ws_max_receive_size, options.logger)
|
70
|
+
@subscriber = Subscriber.new
|
71
|
+
|
72
|
+
start
|
73
|
+
end
|
74
|
+
|
75
|
+
def command(method, async: false, **params)
|
76
|
+
message = build_message(method, params)
|
77
|
+
send_message(message, async: async)
|
78
|
+
end
|
79
|
+
|
80
|
+
def send_message(message, async:)
|
81
|
+
if async
|
82
|
+
@ws.send_message(message)
|
83
|
+
true
|
84
|
+
else
|
85
|
+
pending = Concurrent::IVar.new
|
86
|
+
@pendings[message[:id]] = pending
|
87
|
+
@ws.send_message(message)
|
88
|
+
data = pending.value!(timeout)
|
89
|
+
@pendings.delete(message[:id])
|
90
|
+
|
91
|
+
raise DeadBrowserError if data.nil? && @ws.messages.closed?
|
92
|
+
raise TimeoutError unless data
|
93
|
+
|
94
|
+
error, response = data.values_at("error", "result")
|
95
|
+
raise_browser_error(error) if error
|
96
|
+
response
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def on(event, &block)
|
101
|
+
@subscriber.on(event, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def subscribed?(event)
|
105
|
+
@subscriber.subscribed?(event)
|
106
|
+
end
|
107
|
+
|
108
|
+
def session(session_id)
|
109
|
+
SessionClient.new(self, session_id)
|
110
|
+
end
|
111
|
+
|
112
|
+
def close
|
113
|
+
@ws.close
|
114
|
+
# Give a thread some time to handle a tail of messages
|
115
|
+
@pendings.clear
|
116
|
+
@thread.kill unless @thread.join(1)
|
117
|
+
@subscriber.close
|
118
|
+
end
|
119
|
+
|
120
|
+
def inspect
|
121
|
+
"#<#{self.class} " \
|
122
|
+
"@command_id=#{@command_id.inspect} " \
|
123
|
+
"@pendings=#{@pendings.inspect} " \
|
124
|
+
"@ws=#{@ws.inspect}>"
|
125
|
+
end
|
126
|
+
|
127
|
+
def build_message(method, params)
|
128
|
+
{ method: method, params: params }.merge(id: next_command_id)
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def start
|
134
|
+
@thread = Utils::Thread.spawn do
|
135
|
+
loop do
|
136
|
+
message = @ws.messages.pop
|
137
|
+
break unless message
|
138
|
+
|
139
|
+
if message.key?("method")
|
140
|
+
@subscriber << message
|
141
|
+
else
|
142
|
+
@pendings[message["id"]]&.set(message)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def next_command_id
|
149
|
+
@command_id += 1
|
150
|
+
end
|
151
|
+
|
152
|
+
def raise_browser_error(error)
|
153
|
+
case error["message"]
|
154
|
+
# Node has disappeared while we were trying to get it
|
155
|
+
when "No node with given id found",
|
156
|
+
"Could not find node with given id",
|
157
|
+
"Inspected target navigated or closed"
|
158
|
+
raise NodeNotFoundError, error
|
159
|
+
# Context is lost, page is reloading
|
160
|
+
when "Cannot find context with specified id"
|
161
|
+
raise NoExecutionContextError, error
|
162
|
+
when "No target with given id found"
|
163
|
+
raise NoSuchPageError
|
164
|
+
when /Could not compute content quads/
|
165
|
+
raise CoordinatesNotFoundError
|
166
|
+
else
|
167
|
+
raise BrowserError, error
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/ferrum/context.rb
CHANGED
@@ -8,9 +8,9 @@ module Ferrum
|
|
8
8
|
|
9
9
|
attr_reader :id, :targets
|
10
10
|
|
11
|
-
def initialize(
|
11
|
+
def initialize(client, contexts, id)
|
12
12
|
@id = id
|
13
|
-
@
|
13
|
+
@client = client
|
14
14
|
@contexts = contexts
|
15
15
|
@targets = Concurrent::Map.new
|
16
16
|
@pendings = Concurrent::MVar.new
|
@@ -46,33 +46,37 @@ module Ferrum
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def create_target
|
49
|
-
@
|
50
|
-
|
51
|
-
url: "about:blank")
|
52
|
-
target = @pendings.take(@browser.timeout)
|
49
|
+
@client.command("Target.createTarget", browserContextId: @id, url: "about:blank")
|
50
|
+
target = @pendings.take(@client.timeout)
|
53
51
|
raise NoSuchTargetError unless target.is_a?(Target)
|
54
52
|
|
55
|
-
@targets.put_if_absent(target.id, target)
|
56
53
|
target
|
57
54
|
end
|
58
55
|
|
59
|
-
def add_target(params)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
56
|
+
def add_target(params:, session_id: nil)
|
57
|
+
new_target = Target.new(@client, session_id, params)
|
58
|
+
target = @targets.put_if_absent(new_target.id, new_target)
|
59
|
+
target ||= new_target # `put_if_absent` returns nil if added a new value or existing if there was one already
|
60
|
+
@pendings.put(target, @client.timeout) if @pendings.empty?
|
61
|
+
target
|
66
62
|
end
|
67
63
|
|
68
64
|
def update_target(target_id, params)
|
69
|
-
@targets[target_id]
|
65
|
+
@targets[target_id]&.update(params)
|
70
66
|
end
|
71
67
|
|
72
68
|
def delete_target(target_id)
|
73
69
|
@targets.delete(target_id)
|
74
70
|
end
|
75
71
|
|
72
|
+
def close_targets_connection
|
73
|
+
@targets.each_value do |target|
|
74
|
+
next unless target.connected?
|
75
|
+
|
76
|
+
target.page.close_connection
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
76
80
|
def dispose
|
77
81
|
@contexts.dispose(@id)
|
78
82
|
end
|
data/lib/ferrum/contexts.rb
CHANGED
@@ -4,12 +4,15 @@ require "ferrum/context"
|
|
4
4
|
|
5
5
|
module Ferrum
|
6
6
|
class Contexts
|
7
|
+
include Enumerable
|
8
|
+
|
7
9
|
attr_reader :contexts
|
8
10
|
|
9
|
-
def initialize(
|
11
|
+
def initialize(client)
|
10
12
|
@contexts = Concurrent::Map.new
|
11
|
-
@
|
13
|
+
@client = client
|
12
14
|
subscribe
|
15
|
+
auto_attach
|
13
16
|
discover
|
14
17
|
end
|
15
18
|
|
@@ -17,6 +20,16 @@ module Ferrum
|
|
17
20
|
@default_context ||= create
|
18
21
|
end
|
19
22
|
|
23
|
+
def each(&block)
|
24
|
+
return enum_for(__method__) unless block_given?
|
25
|
+
|
26
|
+
@contexts.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](id)
|
30
|
+
@contexts[id]
|
31
|
+
end
|
32
|
+
|
20
33
|
def find_by(target_id:)
|
21
34
|
context = nil
|
22
35
|
@contexts.each_value { |c| context = c if c.target?(target_id) }
|
@@ -24,21 +37,25 @@ module Ferrum
|
|
24
37
|
end
|
25
38
|
|
26
39
|
def create(**options)
|
27
|
-
response = @
|
40
|
+
response = @client.command("Target.createBrowserContext", **options)
|
28
41
|
context_id = response["browserContextId"]
|
29
|
-
context = Context.new(@
|
42
|
+
context = Context.new(@client, self, context_id)
|
30
43
|
@contexts[context_id] = context
|
31
44
|
context
|
32
45
|
end
|
33
46
|
|
34
47
|
def dispose(context_id)
|
35
48
|
context = @contexts[context_id]
|
36
|
-
|
37
|
-
|
49
|
+
context.close_targets_connection
|
50
|
+
@client.command("Target.disposeBrowserContext", browserContextId: context.id)
|
38
51
|
@contexts.delete(context_id)
|
39
52
|
true
|
40
53
|
end
|
41
54
|
|
55
|
+
def close_connections
|
56
|
+
@contexts.each_value(&:close_targets_connection)
|
57
|
+
end
|
58
|
+
|
42
59
|
def reset
|
43
60
|
@default_context = nil
|
44
61
|
@contexts.each_key { |id| dispose(id) }
|
@@ -51,15 +68,26 @@ module Ferrum
|
|
51
68
|
private
|
52
69
|
|
53
70
|
def subscribe
|
54
|
-
@
|
71
|
+
@client.on("Target.attachedToTarget") do |params|
|
72
|
+
info, session_id = params.values_at("targetInfo", "sessionId")
|
73
|
+
next unless info["type"] == "page"
|
74
|
+
|
75
|
+
context_id = info["browserContextId"]
|
76
|
+
@contexts[context_id]&.add_target(session_id: session_id, params: info)
|
77
|
+
if params["waitingForDebugger"]
|
78
|
+
@client.session(session_id).command("Runtime.runIfWaitingForDebugger", async: true)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
@client.on("Target.targetCreated") do |params|
|
55
83
|
info = params["targetInfo"]
|
56
84
|
next unless info["type"] == "page"
|
57
85
|
|
58
86
|
context_id = info["browserContextId"]
|
59
|
-
@contexts[context_id]&.add_target(info)
|
87
|
+
@contexts[context_id]&.add_target(params: info)
|
60
88
|
end
|
61
89
|
|
62
|
-
@
|
90
|
+
@client.on("Target.targetInfoChanged") do |params|
|
63
91
|
info = params["targetInfo"]
|
64
92
|
next unless info["type"] == "page"
|
65
93
|
|
@@ -67,19 +95,25 @@ module Ferrum
|
|
67
95
|
@contexts[context_id]&.update_target(target_id, info)
|
68
96
|
end
|
69
97
|
|
70
|
-
@
|
98
|
+
@client.on("Target.targetDestroyed") do |params|
|
71
99
|
context = find_by(target_id: params["targetId"])
|
72
100
|
context&.delete_target(params["targetId"])
|
73
101
|
end
|
74
102
|
|
75
|
-
@
|
103
|
+
@client.on("Target.targetCrashed") do |params|
|
76
104
|
context = find_by(target_id: params["targetId"])
|
77
105
|
context&.delete_target(params["targetId"])
|
78
106
|
end
|
79
107
|
end
|
80
108
|
|
81
109
|
def discover
|
82
|
-
@
|
110
|
+
@client.command("Target.setDiscoverTargets", discover: true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def auto_attach
|
114
|
+
return unless @client.options.flatten
|
115
|
+
|
116
|
+
@client.command("Target.setAutoAttach", autoAttach: true, waitForDebuggerOnStart: true, flatten: true)
|
83
117
|
end
|
84
118
|
end
|
85
119
|
end
|
data/lib/ferrum/cookies.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "yaml"
|
3
4
|
require "ferrum/cookies/cookie"
|
4
5
|
|
5
6
|
module Ferrum
|
@@ -169,10 +170,36 @@ module Ferrum
|
|
169
170
|
true
|
170
171
|
end
|
171
172
|
|
173
|
+
#
|
174
|
+
# Stores all cookies of current page in a file.
|
175
|
+
#
|
176
|
+
# @return [Integer]
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# browser.cookies.store # => Integer
|
180
|
+
#
|
181
|
+
def store(path = "cookies.yml")
|
182
|
+
File.write(path, map(&:to_h).to_yaml)
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Loads all cookies from the file and sets them for current page.
|
187
|
+
#
|
188
|
+
# @return [true]
|
189
|
+
#
|
190
|
+
# @example
|
191
|
+
# browser.cookies.load # => true
|
192
|
+
#
|
193
|
+
def load(path = "cookies.yml")
|
194
|
+
cookies = YAML.load_file(path)
|
195
|
+
cookies.each { |c| set(c) }
|
196
|
+
true
|
197
|
+
end
|
198
|
+
|
172
199
|
private
|
173
200
|
|
174
201
|
def default_domain
|
175
|
-
URI.parse(@page.
|
202
|
+
URI.parse(@page.base_url).host if @page.base_url
|
176
203
|
end
|
177
204
|
end
|
178
205
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Downloads
|
5
|
+
VALID_BEHAVIOR = %i[deny allow allowAndName default].freeze
|
6
|
+
|
7
|
+
def initialize(page)
|
8
|
+
@page = page
|
9
|
+
@event = Utils::Event.new.tap(&:set)
|
10
|
+
@files = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def files
|
14
|
+
@files.values
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait(timeout = 5)
|
18
|
+
@event.reset
|
19
|
+
yield if block_given?
|
20
|
+
@event.wait(timeout)
|
21
|
+
@event.set
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_behavior(save_path:, behavior: :allow)
|
25
|
+
raise ArgumentError unless VALID_BEHAVIOR.include?(behavior.to_sym)
|
26
|
+
raise Error, "supply absolute path for `:save_path` option" unless Pathname.new(save_path.to_s).absolute?
|
27
|
+
|
28
|
+
@page.command("Browser.setDownloadBehavior",
|
29
|
+
browserContextId: @page.context_id,
|
30
|
+
downloadPath: save_path,
|
31
|
+
behavior: behavior,
|
32
|
+
eventsEnabled: true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def subscribe
|
36
|
+
subscribe_download_will_begin
|
37
|
+
subscribe_download_progress
|
38
|
+
end
|
39
|
+
|
40
|
+
def subscribe_download_will_begin
|
41
|
+
@page.on("Browser.downloadWillBegin") do |params|
|
42
|
+
@event.reset
|
43
|
+
@files[params["guid"]] = params
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def subscribe_download_progress
|
48
|
+
@page.on("Browser.downloadProgress") do |params|
|
49
|
+
@files[params["guid"]].merge!(params)
|
50
|
+
|
51
|
+
case params["state"]
|
52
|
+
when "completed", "canceled"
|
53
|
+
@event.set
|
54
|
+
else
|
55
|
+
@event.reset
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/ferrum/errors.rb
CHANGED
@@ -6,7 +6,8 @@ module Ferrum
|
|
6
6
|
class NoSuchTargetError < Error; end
|
7
7
|
class NotImplementedError < Error; end
|
8
8
|
class BinaryNotFoundError < Error; end
|
9
|
-
class EmptyPathError
|
9
|
+
class EmptyPathError < Error; end
|
10
|
+
class ServerError < Error; end
|
10
11
|
|
11
12
|
class StatusError < Error
|
12
13
|
def initialize(url, message = nil)
|
@@ -77,6 +78,13 @@ module Ferrum
|
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
81
|
+
class InvalidScreenshotFormatError < Error
|
82
|
+
def initialize(format)
|
83
|
+
valid_formats = Page::Screenshot::SUPPORTED_SCREENSHOT_FORMAT.join(" | ")
|
84
|
+
super("Invalid value #{format} for option `:format` (#{valid_formats})")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
80
88
|
class BrowserError < Error
|
81
89
|
attr_reader :response
|
82
90
|
|
@@ -98,8 +106,7 @@ module Ferrum
|
|
98
106
|
|
99
107
|
class NoExecutionContextError < BrowserError
|
100
108
|
def initialize(response = nil)
|
101
|
-
response
|
102
|
-
super(response)
|
109
|
+
super(response || { "message" => "There's no context available" })
|
103
110
|
end
|
104
111
|
end
|
105
112
|
|
data/lib/ferrum/headers.rb
CHANGED
@@ -68,7 +68,7 @@ module Ferrum
|
|
68
68
|
|
69
69
|
def set_overrides(user_agent: nil, accept_language: nil, platform: nil)
|
70
70
|
options = {}
|
71
|
-
options[:userAgent] = user_agent || @page.
|
71
|
+
options[:userAgent] = user_agent || @page.default_user_agent
|
72
72
|
options[:acceptLanguage] = accept_language if accept_language
|
73
73
|
options[:platform] if platform
|
74
74
|
|
@@ -79,7 +79,7 @@ module Ferrum
|
|
79
79
|
# @return [Boolean]
|
80
80
|
#
|
81
81
|
def finished?
|
82
|
-
blocked? || response&.loaded? || !error.nil?
|
82
|
+
blocked? || response&.loaded? || !error.nil? || ping?
|
83
83
|
end
|
84
84
|
|
85
85
|
#
|
@@ -118,6 +118,15 @@ module Ferrum
|
|
118
118
|
response&.redirect?
|
119
119
|
end
|
120
120
|
|
121
|
+
#
|
122
|
+
# Determines if the exchange is ping.
|
123
|
+
#
|
124
|
+
# @return [Boolean]
|
125
|
+
#
|
126
|
+
def ping?
|
127
|
+
!!request&.ping?
|
128
|
+
end
|
129
|
+
|
121
130
|
#
|
122
131
|
# Returns request's URL.
|
123
132
|
#
|
@@ -10,9 +10,9 @@ module Ferrum
|
|
10
10
|
|
11
11
|
attr_accessor :request_id, :frame_id, :resource_type, :network_id, :status
|
12
12
|
|
13
|
-
def initialize(
|
13
|
+
def initialize(client, params)
|
14
14
|
@status = nil
|
15
|
-
@
|
15
|
+
@client = client
|
16
16
|
@params = params
|
17
17
|
@request_id = params["requestId"]
|
18
18
|
@frame_id = params["frameId"]
|
@@ -43,18 +43,18 @@ module Ferrum
|
|
43
43
|
options = options.merge(body: Base64.strict_encode64(options.fetch(:body, ""))) if has_body
|
44
44
|
|
45
45
|
@status = :responded
|
46
|
-
@
|
46
|
+
@client.command("Fetch.fulfillRequest", async: true, **options)
|
47
47
|
end
|
48
48
|
|
49
49
|
def continue(**options)
|
50
50
|
options = options.merge(requestId: request_id)
|
51
51
|
@status = :continued
|
52
|
-
@
|
52
|
+
@client.command("Fetch.continueRequest", async: true, **options)
|
53
53
|
end
|
54
54
|
|
55
55
|
def abort
|
56
56
|
@status = :aborted
|
57
|
-
@
|
57
|
+
@client.command("Fetch.failRequest", async: true, requestId: request_id, errorReason: "BlockedByClient")
|
58
58
|
end
|
59
59
|
|
60
60
|
def initial_priority
|
@@ -80,6 +80,15 @@ module Ferrum
|
|
80
80
|
@time ||= Time.strptime(@params["wallTime"].to_s, "%s")
|
81
81
|
end
|
82
82
|
|
83
|
+
#
|
84
|
+
# Determines if a request is of type ping.
|
85
|
+
#
|
86
|
+
# @return [Boolean]
|
87
|
+
#
|
88
|
+
def ping?
|
89
|
+
type?("ping")
|
90
|
+
end
|
91
|
+
|
83
92
|
#
|
84
93
|
# Converts the request to a Hash.
|
85
94
|
#
|