Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/selenium-webdriver/bidi/network.js
2884 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
const { BeforeRequestSent, ResponseStarted, FetchError } = require('./networkTypes')
19
const { AddInterceptParameters } = require('./addInterceptParameters')
20
const { ContinueResponseParameters } = require('./continueResponseParameters')
21
const { ContinueRequestParameters } = require('./continueRequestParameters')
22
const { ProvideResponseParameters } = require('./provideResponseParameters')
23
24
const NetworkEvent = {
25
BEFORE_REQUEST_SENT: 'network.beforeRequestSent',
26
RESPONSE_STARTED: 'network.responseStarted',
27
RESPONSE_COMPLETED: 'network.responseCompleted',
28
AUTH_REQUIRED: 'network.authRequired',
29
FETCH_ERROR: 'network.fetchError',
30
}
31
32
const CacheBehavior = Object.freeze({
33
DEFAULT: 'default',
34
BYPASS: 'bypass',
35
})
36
37
/**
38
* Represents all commands and events of Network module.
39
* Described in https://w3c.github.io/webdriver-bidi/#module-network.
40
*/
41
class Network {
42
#callbackId = 0
43
#listener
44
45
/**
46
* Represents a Network object.
47
* @constructor
48
* @param {Driver} driver - The driver to fetch the BiDi connection.
49
* @param {Array} browsingContextIds - An array of browsing context IDs that the network events will be subscribed to.
50
*/
51
constructor(driver, browsingContextIds) {
52
this._driver = driver
53
this._browsingContextIds = browsingContextIds
54
this.#listener = new Map()
55
this.#listener.set(NetworkEvent.AUTH_REQUIRED, new Map())
56
this.#listener.set(NetworkEvent.BEFORE_REQUEST_SENT, new Map())
57
this.#listener.set(NetworkEvent.FETCH_ERROR, new Map())
58
this.#listener.set(NetworkEvent.RESPONSE_STARTED, new Map())
59
this.#listener.set(NetworkEvent.RESPONSE_COMPLETED, new Map())
60
}
61
62
addCallback(eventType, callback) {
63
const id = ++this.#callbackId
64
65
const eventCallbackMap = this.#listener.get(eventType)
66
eventCallbackMap.set(id, callback)
67
return id
68
}
69
70
removeCallback(id) {
71
let hasId = false
72
for (const [, callbacks] of this.#listener) {
73
if (callbacks.has(id)) {
74
callbacks.delete(id)
75
hasId = true
76
}
77
}
78
79
if (!hasId) {
80
throw Error(`Callback with id ${id} not found`)
81
}
82
}
83
84
invokeCallbacks(eventType, data) {
85
const callbacks = this.#listener.get(eventType)
86
if (callbacks) {
87
for (const [, callback] of callbacks) {
88
callback(data)
89
}
90
}
91
}
92
93
async init() {
94
this.bidi = await this._driver.getBidi()
95
}
96
97
/**
98
* Subscribes to the 'network.beforeRequestSent' event and handles it with the provided callback.
99
*
100
* @param {Function} callback - The callback function to handle the event.
101
* @returns {Promise<void>} - A promise that resolves when the subscription is successful.
102
*/
103
async beforeRequestSent(callback) {
104
await this.subscribeAndHandleEvent('network.beforeRequestSent', callback)
105
}
106
107
/**
108
* Subscribes to the 'network.responseStarted' event and handles it with the provided callback.
109
*
110
* @param {Function} callback - The callback function to handle the event.
111
* @returns {Promise<void>} - A promise that resolves when the subscription is successful.
112
*/
113
async responseStarted(callback) {
114
await this.subscribeAndHandleEvent('network.responseStarted', callback)
115
}
116
117
/**
118
* Subscribes to the 'network.responseCompleted' event and handles it with the provided callback.
119
*
120
* @param {Function} callback - The callback function to handle the event.
121
* @returns {Promise<void>} - A promise that resolves when the subscription is successful.
122
*/
123
async responseCompleted(callback) {
124
await this.subscribeAndHandleEvent('network.responseCompleted', callback)
125
}
126
127
/**
128
* Subscribes to the 'network.authRequired' event and handles it with the provided callback.
129
*
130
* @param {Function} callback - The callback function to handle the event.
131
* @returns {Promise<number>} - A promise that resolves when the subscription is successful.
132
*/
133
async authRequired(callback) {
134
return await this.subscribeAndHandleEvent('network.authRequired', callback)
135
}
136
137
/**
138
* Subscribes to the 'network.fetchError' event and handles it with the provided callback.
139
*
140
* @param {Function} callback - The callback function to handle the event.
141
* @returns {Promise<void>} - A promise that resolves when the subscription is successful.
142
*/
143
async fetchError(callback) {
144
await this.subscribeAndHandleEvent('network.fetchError', callback)
145
}
146
147
async subscribeAndHandleEvent(eventType, callback) {
148
if (this._browsingContextIds != null) {
149
await this.bidi.subscribe(eventType, this._browsingContextIds)
150
} else {
151
await this.bidi.subscribe(eventType)
152
}
153
let id = this.addCallback(eventType, callback)
154
155
this.ws = await this.bidi.socket
156
this.ws.on('message', (event) => {
157
const { params } = JSON.parse(Buffer.from(event.toString()))
158
if (params) {
159
let response = null
160
if ('initiator' in params) {
161
response = new BeforeRequestSent(
162
params.context,
163
params.navigation,
164
params.redirectCount,
165
params.request,
166
params.timestamp,
167
params.initiator,
168
)
169
} else if ('response' in params) {
170
response = new ResponseStarted(
171
params.context,
172
params.navigation,
173
params.redirectCount,
174
params.request,
175
params.timestamp,
176
params.response,
177
)
178
} else if ('errorText' in params) {
179
response = new FetchError(
180
params.context,
181
params.navigation,
182
params.redirectCount,
183
params.request,
184
params.timestamp,
185
params.errorText,
186
)
187
}
188
this.invokeCallbacks(eventType, response)
189
}
190
})
191
return id
192
}
193
194
/**
195
* Adds a network intercept.
196
*
197
* @param {AddInterceptParameters} params - The parameters for the network intercept.
198
* @returns {Promise<string>} - A promise that resolves to the added intercept's id.
199
* @throws {Error} - If params is not an instance of AddInterceptParameters.
200
*/
201
async addIntercept(params) {
202
if (!(params instanceof AddInterceptParameters)) {
203
throw new Error(`Params must be an instance of AddInterceptParameters. Received:'${params}'`)
204
}
205
206
const command = {
207
method: 'network.addIntercept',
208
params: Object.fromEntries(params.asMap()),
209
}
210
211
let response = await this.bidi.send(command)
212
213
return response.result.intercept
214
}
215
216
/**
217
* Removes an intercept.
218
*
219
* @param {string} interceptId - The ID of the intercept to be removed.
220
* @returns {Promise<void>} - A promise that resolves when the intercept is successfully removed.
221
*/
222
async removeIntercept(interceptId) {
223
const command = {
224
method: 'network.removeIntercept',
225
params: { intercept: interceptId },
226
}
227
228
await this.bidi.send(command)
229
}
230
231
/**
232
* Continues the network request with authentication credentials.
233
* @param {string} requestId - The ID of the request to continue.
234
* @param {string} username - The username for authentication.
235
* @param {string} password - The password for authentication.
236
* @returns {Promise<void>} - A promise that resolves when the command is sent.
237
*/
238
async continueWithAuth(requestId, username, password) {
239
const command = {
240
method: 'network.continueWithAuth',
241
params: {
242
request: requestId.toString(),
243
action: 'provideCredentials',
244
credentials: {
245
type: 'password',
246
username: username,
247
password: password,
248
},
249
},
250
}
251
await this.bidi.send(command)
252
}
253
254
/**
255
* Fails a network request.
256
*
257
* @param {number} requestId - The ID of the request to fail.
258
* @returns {Promise<void>} - A promise that resolves when the command is sent.
259
*/
260
async failRequest(requestId) {
261
const command = {
262
method: 'network.failRequest',
263
params: {
264
request: requestId.toString(),
265
},
266
}
267
await this.bidi.send(command)
268
}
269
270
/**
271
* Continues the network request with authentication but without providing credentials.
272
* @param {string} requestId - The ID of the request to continue with authentication.
273
* @returns {Promise<void>} - A promise that resolves when the command is sent.
274
*/
275
async continueWithAuthNoCredentials(requestId) {
276
const command = {
277
method: 'network.continueWithAuth',
278
params: {
279
request: requestId.toString(),
280
action: 'default',
281
},
282
}
283
await this.bidi.send(command)
284
}
285
286
/**
287
* Cancels the authentication for a specific request.
288
*
289
* @param {string} requestId - The ID of the request to cancel authentication for.
290
* @returns {Promise<void>} - A promise that resolves when the command is sent.
291
*/
292
async cancelAuth(requestId) {
293
const command = {
294
method: 'network.continueWithAuth',
295
params: {
296
request: requestId.toString(),
297
action: 'cancel',
298
},
299
}
300
await this.bidi.send(command)
301
}
302
303
/**
304
* Continues the network request with the provided parameters.
305
*
306
* @param {ContinueRequestParameters} params - The parameters for continuing the request.
307
* @throws {Error} If params is not an instance of ContinueRequestParameters.
308
* @returns {Promise<void>} A promise that resolves when the command is sent.
309
*/
310
async continueRequest(params) {
311
if (!(params instanceof ContinueRequestParameters)) {
312
throw new Error(`Params must be an instance of ContinueRequestParameters. Received:'${params}'`)
313
}
314
315
const command = {
316
method: 'network.continueRequest',
317
params: Object.fromEntries(params.asMap()),
318
}
319
320
await this.bidi.send(command)
321
}
322
323
/**
324
* Continues the network response with the given parameters.
325
*
326
* @param {ContinueResponseParameters} params - The parameters for continuing the response.
327
* @throws {Error} If params is not an instance of ContinueResponseParameters.
328
* @returns {Promise<void>} A promise that resolves when the command is sent.
329
*/
330
async continueResponse(params) {
331
if (!(params instanceof ContinueResponseParameters)) {
332
throw new Error(`Params must be an instance of ContinueResponseParameters. Received:'${params}'`)
333
}
334
335
const command = {
336
method: 'network.continueResponse',
337
params: Object.fromEntries(params.asMap()),
338
}
339
340
await this.bidi.send(command)
341
}
342
343
/**
344
* Provides a response for the network.
345
*
346
* @param {ProvideResponseParameters} params - The parameters for providing the response.
347
* @throws {Error} If params is not an instance of ProvideResponseParameters.
348
* @returns {Promise<void>} A promise that resolves when the command is sent.
349
*/
350
async provideResponse(params) {
351
if (!(params instanceof ProvideResponseParameters)) {
352
throw new Error(`Params must be an instance of ProvideResponseParameters. Received:'${params}'`)
353
}
354
355
const command = {
356
method: 'network.provideResponse',
357
params: Object.fromEntries(params.asMap()),
358
}
359
360
await this.bidi.send(command)
361
}
362
363
/**
364
* Sets the cache behavior for network requests.
365
*
366
* @param {string} behavior - The cache behavior ("default" or "bypass")
367
* @param {Array<string>} [contexts] - Optional array of browsing context IDs
368
* @returns {Promise<void>} A promise that resolves when the cache behavior is set
369
* @throws {Error} If behavior is invalid or context IDs are invalid
370
*/
371
async setCacheBehavior(behavior, contexts = null) {
372
if (!Object.values(CacheBehavior).includes(behavior)) {
373
throw new Error(`Cache behavior must be either "${CacheBehavior.DEFAULT}" or "${CacheBehavior.BYPASS}"`)
374
}
375
376
const command = {
377
method: 'network.setCacheBehavior',
378
params: {
379
cacheBehavior: behavior,
380
},
381
}
382
383
if (contexts !== null) {
384
if (
385
!Array.isArray(contexts) ||
386
contexts.length === 0 ||
387
contexts.some((c) => typeof c !== 'string' || c.trim() === '')
388
) {
389
throw new Error('Contexts must be an array of non-empty strings')
390
}
391
command.params.contexts = contexts
392
}
393
394
await this.bidi.send(command)
395
}
396
397
/**
398
* Unsubscribes from network events for all browsing contexts.
399
* @returns {Promise<void>} A promise that resolves when the network connection is closed.
400
*/
401
async close() {
402
if (
403
this._browsingContextIds !== null &&
404
this._browsingContextIds !== undefined &&
405
this._browsingContextIds.length > 0
406
) {
407
await this.bidi.unsubscribe(
408
'network.beforeRequestSent',
409
'network.responseStarted',
410
'network.responseCompleted',
411
'network.authRequired',
412
this._browsingContextIds,
413
)
414
} else {
415
await this.bidi.unsubscribe(
416
'network.beforeRequestSent',
417
'network.responseStarted',
418
'network.responseCompleted',
419
'network.authRequired',
420
)
421
}
422
}
423
}
424
425
async function getNetworkInstance(driver, browsingContextIds = null) {
426
let instance = new Network(driver, browsingContextIds)
427
await instance.init()
428
return instance
429
}
430
431
module.exports = { Network: getNetworkInstance, CacheBehavior }
432
433