puppeteer-bidi 0.0.1.beta10 → 0.0.1
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/AGENTS.md +44 -0
- data/API_COVERAGE.md +345 -0
- data/CLAUDE/porting_puppeteer.md +20 -0
- data/CLAUDE.md +2 -1
- data/DEVELOPMENT.md +14 -0
- data/README.md +47 -415
- data/development/generate_api_coverage.rb +411 -0
- data/development/puppeteer_revision.txt +1 -0
- data/lib/puppeteer/bidi/browser.rb +118 -22
- data/lib/puppeteer/bidi/browser_context.rb +185 -2
- data/lib/puppeteer/bidi/connection.rb +16 -5
- data/lib/puppeteer/bidi/cookie_utils.rb +192 -0
- data/lib/puppeteer/bidi/core/browsing_context.rb +83 -40
- data/lib/puppeteer/bidi/core/realm.rb +6 -0
- data/lib/puppeteer/bidi/core/request.rb +79 -35
- data/lib/puppeteer/bidi/core/user_context.rb +5 -3
- data/lib/puppeteer/bidi/element_handle.rb +200 -8
- data/lib/puppeteer/bidi/errors.rb +4 -0
- data/lib/puppeteer/bidi/frame.rb +115 -11
- data/lib/puppeteer/bidi/http_request.rb +577 -0
- data/lib/puppeteer/bidi/http_response.rb +161 -10
- data/lib/puppeteer/bidi/locator.rb +792 -0
- data/lib/puppeteer/bidi/page.rb +859 -7
- data/lib/puppeteer/bidi/query_handler.rb +1 -1
- data/lib/puppeteer/bidi/version.rb +1 -1
- data/lib/puppeteer/bidi.rb +39 -6
- data/sig/puppeteer/bidi/browser.rbs +53 -6
- data/sig/puppeteer/bidi/browser_context.rbs +36 -0
- data/sig/puppeteer/bidi/cookie_utils.rbs +64 -0
- data/sig/puppeteer/bidi/core/browsing_context.rbs +16 -6
- data/sig/puppeteer/bidi/core/request.rbs +14 -11
- data/sig/puppeteer/bidi/core/user_context.rbs +2 -2
- data/sig/puppeteer/bidi/element_handle.rbs +28 -0
- data/sig/puppeteer/bidi/errors.rbs +4 -0
- data/sig/puppeteer/bidi/frame.rbs +17 -0
- data/sig/puppeteer/bidi/http_request.rbs +162 -0
- data/sig/puppeteer/bidi/http_response.rbs +67 -8
- data/sig/puppeteer/bidi/locator.rbs +267 -0
- data/sig/puppeteer/bidi/page.rbs +170 -0
- data/sig/puppeteer/bidi.rbs +15 -3
- metadata +12 -1
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
module Puppeteer
|
|
7
|
+
module Bidi
|
|
8
|
+
# HTTPRequest represents a request initiated by a page.
|
|
9
|
+
class HTTPRequest
|
|
10
|
+
module InterceptResolutionAction
|
|
11
|
+
ABORT = "abort"
|
|
12
|
+
RESPOND = "respond"
|
|
13
|
+
CONTINUE = "continue"
|
|
14
|
+
DISABLED = "disabled"
|
|
15
|
+
NONE = "none"
|
|
16
|
+
ALREADY_HANDLED = "already-handled"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
DEFAULT_INTERCEPT_RESOLUTION_PRIORITY = 0
|
|
20
|
+
|
|
21
|
+
STATUS_TEXTS = {
|
|
22
|
+
"100" => "Continue",
|
|
23
|
+
"101" => "Switching Protocols",
|
|
24
|
+
"102" => "Processing",
|
|
25
|
+
"103" => "Early Hints",
|
|
26
|
+
"200" => "OK",
|
|
27
|
+
"201" => "Created",
|
|
28
|
+
"202" => "Accepted",
|
|
29
|
+
"203" => "Non-Authoritative Information",
|
|
30
|
+
"204" => "No Content",
|
|
31
|
+
"205" => "Reset Content",
|
|
32
|
+
"206" => "Partial Content",
|
|
33
|
+
"207" => "Multi-Status",
|
|
34
|
+
"208" => "Already Reported",
|
|
35
|
+
"226" => "IM Used",
|
|
36
|
+
"300" => "Multiple Choices",
|
|
37
|
+
"301" => "Moved Permanently",
|
|
38
|
+
"302" => "Found",
|
|
39
|
+
"303" => "See Other",
|
|
40
|
+
"304" => "Not Modified",
|
|
41
|
+
"305" => "Use Proxy",
|
|
42
|
+
"306" => "Switch Proxy",
|
|
43
|
+
"307" => "Temporary Redirect",
|
|
44
|
+
"308" => "Permanent Redirect",
|
|
45
|
+
"400" => "Bad Request",
|
|
46
|
+
"401" => "Unauthorized",
|
|
47
|
+
"402" => "Payment Required",
|
|
48
|
+
"403" => "Forbidden",
|
|
49
|
+
"404" => "Not Found",
|
|
50
|
+
"405" => "Method Not Allowed",
|
|
51
|
+
"406" => "Not Acceptable",
|
|
52
|
+
"407" => "Proxy Authentication Required",
|
|
53
|
+
"408" => "Request Timeout",
|
|
54
|
+
"409" => "Conflict",
|
|
55
|
+
"410" => "Gone",
|
|
56
|
+
"411" => "Length Required",
|
|
57
|
+
"412" => "Precondition Failed",
|
|
58
|
+
"413" => "Payload Too Large",
|
|
59
|
+
"414" => "URI Too Long",
|
|
60
|
+
"415" => "Unsupported Media Type",
|
|
61
|
+
"416" => "Range Not Satisfiable",
|
|
62
|
+
"417" => "Expectation Failed",
|
|
63
|
+
"418" => "I'm a teapot",
|
|
64
|
+
"421" => "Misdirected Request",
|
|
65
|
+
"422" => "Unprocessable Entity",
|
|
66
|
+
"423" => "Locked",
|
|
67
|
+
"424" => "Failed Dependency",
|
|
68
|
+
"425" => "Too Early",
|
|
69
|
+
"426" => "Upgrade Required",
|
|
70
|
+
"428" => "Precondition Required",
|
|
71
|
+
"429" => "Too Many Requests",
|
|
72
|
+
"431" => "Request Header Fields Too Large",
|
|
73
|
+
"451" => "Unavailable For Legal Reasons",
|
|
74
|
+
"500" => "Internal Server Error",
|
|
75
|
+
"501" => "Not Implemented",
|
|
76
|
+
"502" => "Bad Gateway",
|
|
77
|
+
"503" => "Service Unavailable",
|
|
78
|
+
"504" => "Gateway Timeout",
|
|
79
|
+
"505" => "HTTP Version Not Supported",
|
|
80
|
+
"506" => "Variant Also Negotiates",
|
|
81
|
+
"507" => "Insufficient Storage",
|
|
82
|
+
"508" => "Loop Detected",
|
|
83
|
+
"510" => "Not Extended",
|
|
84
|
+
"511" => "Network Authentication Required",
|
|
85
|
+
}.freeze
|
|
86
|
+
|
|
87
|
+
ERROR_REASONS = {
|
|
88
|
+
"aborted" => "Aborted",
|
|
89
|
+
"accessdenied" => "AccessDenied",
|
|
90
|
+
"addressunreachable" => "AddressUnreachable",
|
|
91
|
+
"blockedbyclient" => "BlockedByClient",
|
|
92
|
+
"blockedbyresponse" => "BlockedByResponse",
|
|
93
|
+
"connectionaborted" => "ConnectionAborted",
|
|
94
|
+
"connectionclosed" => "ConnectionClosed",
|
|
95
|
+
"connectionfailed" => "ConnectionFailed",
|
|
96
|
+
"connectionrefused" => "ConnectionRefused",
|
|
97
|
+
"connectionreset" => "ConnectionReset",
|
|
98
|
+
"internetdisconnected" => "InternetDisconnected",
|
|
99
|
+
"namenotresolved" => "NameNotResolved",
|
|
100
|
+
"timedout" => "TimedOut",
|
|
101
|
+
"failed" => "Failed",
|
|
102
|
+
}.freeze
|
|
103
|
+
|
|
104
|
+
REQUESTS = begin
|
|
105
|
+
ObjectSpace::WeakMap.new
|
|
106
|
+
rescue NameError
|
|
107
|
+
{}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @rbs core_request: Core::Request -- Underlying BiDi request
|
|
111
|
+
# @rbs frame: Frame -- Owning frame
|
|
112
|
+
# @rbs interception_enabled: bool -- Whether interception is enabled
|
|
113
|
+
# @rbs redirect: HTTPRequest? -- Redirected request
|
|
114
|
+
# @rbs return: HTTPRequest
|
|
115
|
+
def self.from(core_request, frame, interception_enabled, redirect: nil)
|
|
116
|
+
request = new(core_request, frame, interception_enabled, redirect)
|
|
117
|
+
request.send(:initialize_request)
|
|
118
|
+
request
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs core_request: Core::Request -- Underlying core request
|
|
122
|
+
# @rbs return: HTTPRequest? -- Mapped request
|
|
123
|
+
def self.for_core_request(core_request)
|
|
124
|
+
REQUESTS[core_request]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
attr_reader :id
|
|
128
|
+
|
|
129
|
+
# @rbs core_request: Core::Request -- Underlying BiDi request
|
|
130
|
+
# @rbs frame: Frame -- Owning frame
|
|
131
|
+
# @rbs interception_enabled: bool -- Whether interception is enabled
|
|
132
|
+
# @rbs redirect: HTTPRequest? -- Redirected request
|
|
133
|
+
# @rbs return: void
|
|
134
|
+
def initialize(core_request, frame, interception_enabled, redirect)
|
|
135
|
+
@request = core_request
|
|
136
|
+
@frame = frame
|
|
137
|
+
@redirect_chain = redirect ? redirect.send(:redirect_chain_internal) : []
|
|
138
|
+
@response = nil
|
|
139
|
+
@authentication_handled = false
|
|
140
|
+
@from_memory_cache = false
|
|
141
|
+
@id = core_request.id
|
|
142
|
+
|
|
143
|
+
@interception = {
|
|
144
|
+
enabled: interception_enabled,
|
|
145
|
+
handled: false,
|
|
146
|
+
handlers: [],
|
|
147
|
+
resolution_state: { action: InterceptResolutionAction::NONE },
|
|
148
|
+
request_overrides: {},
|
|
149
|
+
response: nil,
|
|
150
|
+
abort_reason: nil,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
REQUESTS[@request] = self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @rbs return: String -- Request URL
|
|
157
|
+
def url
|
|
158
|
+
@request.url
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @rbs return: String -- HTTP method
|
|
162
|
+
def method
|
|
163
|
+
@request.method
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @rbs return: Hash[String, String] -- Lowercased headers
|
|
167
|
+
def headers
|
|
168
|
+
headers = {}
|
|
169
|
+
@request.headers.each do |header|
|
|
170
|
+
name = header["name"].to_s.downcase
|
|
171
|
+
value = header["value"]
|
|
172
|
+
next unless value.is_a?(Hash)
|
|
173
|
+
next unless value["type"] == "string"
|
|
174
|
+
|
|
175
|
+
headers[name] = value["value"]
|
|
176
|
+
end
|
|
177
|
+
headers.dup
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @rbs return: String? -- POST body (if available)
|
|
181
|
+
def post_data
|
|
182
|
+
@request.post_data
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @rbs return: bool -- Whether request has POST data
|
|
186
|
+
def has_post_data?
|
|
187
|
+
@request.has_post_data?
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# @rbs return: String? -- POST body fetched from browser
|
|
191
|
+
def fetch_post_data
|
|
192
|
+
@request.fetch_post_data.wait
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @rbs return: String -- Resource type
|
|
196
|
+
def resource_type
|
|
197
|
+
(@request.resource_type || "other").downcase
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @rbs return: Frame -- Request initiator frame
|
|
201
|
+
def frame
|
|
202
|
+
@frame
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @rbs return: bool -- Whether this is a navigation request
|
|
206
|
+
def navigation_request?
|
|
207
|
+
!@request.navigation.nil?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# @rbs return: Array[HTTPRequest] -- Redirect chain
|
|
211
|
+
def redirect_chain
|
|
212
|
+
@redirect_chain.dup
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @rbs return: HTTPResponse? -- Response if available
|
|
216
|
+
def response
|
|
217
|
+
@response
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @rbs return: Hash[String, String]? -- Failure info
|
|
221
|
+
def failure
|
|
222
|
+
return nil if @request.error.nil?
|
|
223
|
+
|
|
224
|
+
{ "errorText" => @request.error }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @rbs return: Hash[String, untyped]? -- Initiator metadata
|
|
228
|
+
def initiator
|
|
229
|
+
initiator = @request.initiator
|
|
230
|
+
return nil unless initiator
|
|
231
|
+
|
|
232
|
+
normalized = {}
|
|
233
|
+
initiator.each do |key, value|
|
|
234
|
+
normalized[key.to_s] = value
|
|
235
|
+
end
|
|
236
|
+
normalized["type"] ||= "other"
|
|
237
|
+
normalized
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @rbs return: Hash[String, untyped] -- Timing info
|
|
241
|
+
def timing
|
|
242
|
+
@request.timing
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @rbs return: Hash[Symbol, untyped] -- Interception resolution state
|
|
246
|
+
def intercept_resolution_state
|
|
247
|
+
return { action: InterceptResolutionAction::DISABLED } unless @request.blocked?
|
|
248
|
+
|
|
249
|
+
if !@interception[:enabled]
|
|
250
|
+
return { action: InterceptResolutionAction::DISABLED }
|
|
251
|
+
end
|
|
252
|
+
if @interception[:handled]
|
|
253
|
+
return { action: InterceptResolutionAction::ALREADY_HANDLED }
|
|
254
|
+
end
|
|
255
|
+
@interception[:resolution_state].dup
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# @rbs return: bool -- Whether intercept is already handled
|
|
259
|
+
def intercept_resolution_handled?
|
|
260
|
+
@interception[:handled]
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# @rbs return: Hash[Symbol | String, untyped] -- Continue overrides
|
|
264
|
+
def continue_request_overrides
|
|
265
|
+
@interception[:request_overrides]
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @rbs return: Hash[Symbol | String, untyped]? -- Response overrides
|
|
269
|
+
def response_for_request
|
|
270
|
+
@interception[:response]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# @rbs return: String? -- Abort error reason
|
|
274
|
+
def abort_error_reason
|
|
275
|
+
@interception[:abort_reason]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# @rbs &block: (-> untyped) -- Intercept handler
|
|
279
|
+
# @rbs return: void
|
|
280
|
+
def enqueue_intercept_action(&block)
|
|
281
|
+
@interception[:handlers] << block
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# @rbs return: void
|
|
285
|
+
def finalize_interceptions
|
|
286
|
+
@interception[:handlers].each do |handler|
|
|
287
|
+
AsyncUtils.await(handler.call)
|
|
288
|
+
end
|
|
289
|
+
@interception[:handlers] = []
|
|
290
|
+
|
|
291
|
+
case intercept_resolution_state[:action]
|
|
292
|
+
when InterceptResolutionAction::ABORT
|
|
293
|
+
_abort(@interception[:abort_reason])
|
|
294
|
+
when InterceptResolutionAction::RESPOND
|
|
295
|
+
raise "Response is missing for the interception" if @interception[:response].nil?
|
|
296
|
+
|
|
297
|
+
_respond(@interception[:response])
|
|
298
|
+
when InterceptResolutionAction::CONTINUE
|
|
299
|
+
_continue(@interception[:request_overrides])
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# @rbs overrides: Hash[Symbol | String, untyped] -- Continue overrides
|
|
304
|
+
# @rbs priority: Integer? -- Cooperative intercept priority
|
|
305
|
+
# @rbs return: void
|
|
306
|
+
def continue(overrides = {}, priority = nil)
|
|
307
|
+
verify_interception
|
|
308
|
+
return unless can_be_intercepted?
|
|
309
|
+
|
|
310
|
+
if priority.nil?
|
|
311
|
+
return _continue(overrides)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
@interception[:request_overrides] = overrides
|
|
315
|
+
if @interception[:resolution_state][:priority].nil? || priority > @interception[:resolution_state][:priority]
|
|
316
|
+
@interception[:resolution_state] = { action: InterceptResolutionAction::CONTINUE, priority: priority }
|
|
317
|
+
return
|
|
318
|
+
end
|
|
319
|
+
if priority == @interception[:resolution_state][:priority]
|
|
320
|
+
return if [InterceptResolutionAction::ABORT, InterceptResolutionAction::RESPOND].include?(
|
|
321
|
+
@interception[:resolution_state][:action],
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
@interception[:resolution_state][:action] = InterceptResolutionAction::CONTINUE
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# @rbs response: Hash[Symbol | String, untyped] -- Response overrides
|
|
329
|
+
# @rbs priority: Integer? -- Cooperative intercept priority
|
|
330
|
+
# @rbs return: void
|
|
331
|
+
def respond(response = {}, priority = nil)
|
|
332
|
+
verify_interception
|
|
333
|
+
return unless can_be_intercepted?
|
|
334
|
+
|
|
335
|
+
if priority.nil?
|
|
336
|
+
return _respond(response)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
@interception[:response] = response
|
|
340
|
+
if @interception[:resolution_state][:priority].nil? || priority > @interception[:resolution_state][:priority]
|
|
341
|
+
@interception[:resolution_state] = { action: InterceptResolutionAction::RESPOND, priority: priority }
|
|
342
|
+
return
|
|
343
|
+
end
|
|
344
|
+
if priority == @interception[:resolution_state][:priority]
|
|
345
|
+
return if @interception[:resolution_state][:action] == InterceptResolutionAction::ABORT
|
|
346
|
+
|
|
347
|
+
@interception[:resolution_state][:action] = InterceptResolutionAction::RESPOND
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# @rbs error_code: String -- Abort error code
|
|
352
|
+
# @rbs priority: Integer? -- Cooperative intercept priority
|
|
353
|
+
# @rbs return: void
|
|
354
|
+
def abort(error_code = "failed", priority = nil)
|
|
355
|
+
verify_interception
|
|
356
|
+
return unless can_be_intercepted?
|
|
357
|
+
|
|
358
|
+
error_reason = ERROR_REASONS[error_code]
|
|
359
|
+
raise Error, "Unknown error code: #{error_code}" unless error_reason
|
|
360
|
+
|
|
361
|
+
if priority.nil?
|
|
362
|
+
return _abort(error_reason)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
@interception[:abort_reason] = error_reason
|
|
366
|
+
if @interception[:resolution_state][:priority].nil? || priority >= @interception[:resolution_state][:priority]
|
|
367
|
+
@interception[:resolution_state] = { action: InterceptResolutionAction::ABORT, priority: priority }
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# @rbs return: String -- Response body (binary string)
|
|
372
|
+
def get_response_content
|
|
373
|
+
@request.response_content.wait
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# @rbs body: String -- Response body
|
|
377
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
378
|
+
def self.get_response(body)
|
|
379
|
+
bytes = body.is_a?(String) ? body.dup.force_encoding("BINARY") : body
|
|
380
|
+
{
|
|
381
|
+
content_length: bytes.bytesize,
|
|
382
|
+
base64: Base64.strict_encode64(bytes),
|
|
383
|
+
}
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
private
|
|
387
|
+
|
|
388
|
+
def initialize_request
|
|
389
|
+
@request.on(:redirect) do |redirect_request|
|
|
390
|
+
http_request = HTTPRequest.from(
|
|
391
|
+
redirect_request,
|
|
392
|
+
@frame,
|
|
393
|
+
@interception[:enabled],
|
|
394
|
+
redirect: self,
|
|
395
|
+
)
|
|
396
|
+
@redirect_chain << self
|
|
397
|
+
|
|
398
|
+
redirect_request.once(:success) do
|
|
399
|
+
@frame.page.emit(:requestfinished, http_request)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
redirect_request.once(:error) do
|
|
403
|
+
@frame.page.emit(:requestfailed, http_request)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
Async do
|
|
407
|
+
http_request.finalize_interceptions
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
@request.once(:response) do |data|
|
|
412
|
+
@response = HTTPResponse.from(data, self)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
@request.once(:success) do |data|
|
|
416
|
+
@response = HTTPResponse.from(data, self)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
@request.on(:authenticate) do
|
|
420
|
+
handle_authentication
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
@frame.page.emit(:request, self)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def redirect_chain_internal
|
|
427
|
+
@redirect_chain
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def verify_interception
|
|
431
|
+
raise Error, "Request Interception is not enabled!" unless @interception[:enabled]
|
|
432
|
+
raise Error, "Request is already handled!" if @interception[:handled]
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def can_be_intercepted?
|
|
436
|
+
@request.blocked?
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def _continue(overrides)
|
|
440
|
+
headers = self.class.bidi_headers_from_hash(value_for_key(overrides, :headers, "headers"))
|
|
441
|
+
@interception[:handled] = true
|
|
442
|
+
|
|
443
|
+
begin
|
|
444
|
+
@request.continue_request(
|
|
445
|
+
url: value_for_key(overrides, :url, "url"),
|
|
446
|
+
method: value_for_key(overrides, :method, "method"),
|
|
447
|
+
body: body_override(value_for_key(overrides, :postData, "postData")),
|
|
448
|
+
headers: headers.empty? ? nil : headers,
|
|
449
|
+
).wait
|
|
450
|
+
rescue => error
|
|
451
|
+
@interception[:handled] = false
|
|
452
|
+
self.class.handle_interception_error(error)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def _abort(_error_reason)
|
|
457
|
+
@interception[:handled] = true
|
|
458
|
+
begin
|
|
459
|
+
@request.fail_request.wait
|
|
460
|
+
rescue => error
|
|
461
|
+
@interception[:handled] = false
|
|
462
|
+
raise error
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def _respond(response)
|
|
467
|
+
@interception[:handled] = true
|
|
468
|
+
|
|
469
|
+
parsed_body = nil
|
|
470
|
+
body = value_for_key(response, :body, "body")
|
|
471
|
+
parsed_body = self.class.get_response(body) if body
|
|
472
|
+
|
|
473
|
+
headers = self.class.bidi_headers_from_hash(value_for_key(response, :headers, "headers"))
|
|
474
|
+
has_content_length = headers.any? { |header| header["name"] == "content-length" }
|
|
475
|
+
|
|
476
|
+
content_type = value_for_key(response, :contentType, "contentType")
|
|
477
|
+
if content_type
|
|
478
|
+
headers << {
|
|
479
|
+
"name" => "content-type",
|
|
480
|
+
"value" => { "type" => "string", "value" => content_type.to_s },
|
|
481
|
+
}
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
if parsed_body && !has_content_length
|
|
485
|
+
headers << {
|
|
486
|
+
"name" => "content-length",
|
|
487
|
+
"value" => { "type" => "string", "value" => parsed_body[:content_length].to_s },
|
|
488
|
+
}
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
status = value_for_key(response, :status, "status") || 200
|
|
492
|
+
|
|
493
|
+
begin
|
|
494
|
+
@request.provide_response(
|
|
495
|
+
status_code: status,
|
|
496
|
+
reason_phrase: STATUS_TEXTS[status.to_s],
|
|
497
|
+
headers: headers.empty? ? nil : headers,
|
|
498
|
+
body: parsed_body ? { type: "base64", value: parsed_body[:base64] } : nil,
|
|
499
|
+
).wait
|
|
500
|
+
rescue => error
|
|
501
|
+
@interception[:handled] = false
|
|
502
|
+
raise error
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def handle_authentication
|
|
507
|
+
credentials = @frame.page.credentials
|
|
508
|
+
if credentials && !@authentication_handled
|
|
509
|
+
@authentication_handled = true
|
|
510
|
+
@request.continue_with_auth(
|
|
511
|
+
action: "provideCredentials",
|
|
512
|
+
credentials: {
|
|
513
|
+
"type" => "password",
|
|
514
|
+
"username" => credentials[:username],
|
|
515
|
+
"password" => credentials[:password],
|
|
516
|
+
},
|
|
517
|
+
).wait
|
|
518
|
+
else
|
|
519
|
+
@request.continue_with_auth(action: "cancel").wait
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def body_override(post_data)
|
|
524
|
+
return nil if post_data.nil?
|
|
525
|
+
|
|
526
|
+
{
|
|
527
|
+
type: "base64",
|
|
528
|
+
value: Base64.strict_encode64(post_data.to_s.b),
|
|
529
|
+
}
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def value_for_key(hash, symbol_key, string_key)
|
|
533
|
+
return nil unless hash
|
|
534
|
+
|
|
535
|
+
if hash.key?(symbol_key)
|
|
536
|
+
hash[symbol_key]
|
|
537
|
+
elsif hash.key?(string_key)
|
|
538
|
+
hash[string_key]
|
|
539
|
+
else
|
|
540
|
+
nil
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def self.bidi_headers_from_hash(raw_headers)
|
|
545
|
+
headers = []
|
|
546
|
+
(raw_headers || {}).each do |name, value|
|
|
547
|
+
next if value.nil?
|
|
548
|
+
|
|
549
|
+
values = value.is_a?(Array) ? value : [value]
|
|
550
|
+
values.each do |header_value|
|
|
551
|
+
headers << {
|
|
552
|
+
"name" => name.to_s.downcase,
|
|
553
|
+
"value" => {
|
|
554
|
+
"type" => "string",
|
|
555
|
+
"value" => header_value.to_s,
|
|
556
|
+
},
|
|
557
|
+
}
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
headers
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def self.handle_interception_error(error)
|
|
564
|
+
message = error.message.to_s
|
|
565
|
+
if message.include?("Invalid header") ||
|
|
566
|
+
message.include?("Unsafe header") ||
|
|
567
|
+
message.include?('Expected "header"') ||
|
|
568
|
+
message.include?("invalid argument")
|
|
569
|
+
raise error
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
warn(error.full_message) if ENV["DEBUG_BIDI_COMMAND"]
|
|
573
|
+
nil
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
end
|