Rename to hkt.sh
This commit is contained in:
18
node_modules/chrome-remote-interface/LICENSE
generated
vendored
Normal file
18
node_modules/chrome-remote-interface/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
Copyright (c) 2026 Andrea Cardaci <cyrus.and@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
991
node_modules/chrome-remote-interface/README.md
generated
vendored
Normal file
991
node_modules/chrome-remote-interface/README.md
generated
vendored
Normal file
@@ -0,0 +1,991 @@
|
||||
# chrome-remote-interface
|
||||
|
||||
[](https://github.com/cyrus-and/chrome-remote-interface/actions?query=workflow:CI)
|
||||
|
||||
[Chrome Debugging Protocol] interface that helps to instrument Chrome (or any
|
||||
other suitable [implementation](#implementations)) by providing a simple
|
||||
abstraction of commands and notifications using a straightforward JavaScript
|
||||
API.
|
||||
|
||||
## Sample API usage
|
||||
|
||||
The following snippet loads `https://github.com` and dumps every request made:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
|
||||
async function example() {
|
||||
let client;
|
||||
try {
|
||||
// connect to endpoint
|
||||
client = await CDP();
|
||||
// extract domains
|
||||
const {Network, Page} = client;
|
||||
// setup handlers
|
||||
Network.requestWillBeSent((params) => {
|
||||
console.log(params.request.url);
|
||||
});
|
||||
// enable events then start!
|
||||
await Network.enable();
|
||||
await Page.enable();
|
||||
await Page.navigate({url: 'https://github.com'});
|
||||
await Page.loadEventFired();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
if (client) {
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
example();
|
||||
```
|
||||
|
||||
Find more examples in the [wiki]. You may also want to take a look at the [FAQ].
|
||||
|
||||
[wiki]: https://github.com/cyrus-and/chrome-remote-interface/wiki
|
||||
[async-await-example]: https://github.com/cyrus-and/chrome-remote-interface/wiki/Async-await-example
|
||||
[FAQ]: https://github.com/cyrus-and/chrome-remote-interface#faq
|
||||
|
||||
## Installation
|
||||
|
||||
npm install chrome-remote-interface
|
||||
|
||||
Install globally (`-g`) to just use the [bundled client](#bundled-client).
|
||||
|
||||
## Implementations
|
||||
|
||||
This module should work with every application implementing the
|
||||
[Chrome Debugging Protocol]. In particular, it has been tested against the
|
||||
following implementations:
|
||||
|
||||
Implementation | Protocol version | [Protocol] | [List] | [New] | [Activate] | [Close] | [Version]
|
||||
---------------------------|--------------------|------------|--------|-------|------------|---------|-----------
|
||||
[Chrome][1.1] | [tip-of-tree][1.2] | yes¹ | yes | yes | yes | yes | yes
|
||||
[Opera][2.1] | [tip-of-tree][2.2] | yes | yes | yes | yes | yes | yes
|
||||
[Node.js][3.1] ([v6.3.0]+) | [node][3.2] | yes | no | no | no | no | yes
|
||||
[Safari (iOS)][4.1] | [*partial*][4.2] | no | yes | no | no | no | no
|
||||
[Edge][5.1] | [*partial*][5.2] | yes | yes | no | no | no | yes
|
||||
[Firefox (Nightly)][6.1] | [*partial*][6.2] | yes | yes | no | yes | yes | yes
|
||||
|
||||
¹ Not available on [Chrome for Android][chrome-mobile-protocol], hence a local version of the protocol must be used.
|
||||
|
||||
[chrome-mobile-protocol]: https://bugs.chromium.org/p/chromium/issues/detail?id=824626#c4
|
||||
|
||||
[1.1]: #chromechromium
|
||||
[1.2]: https://chromedevtools.github.io/devtools-protocol/tot/
|
||||
|
||||
[2.1]: #opera
|
||||
[2.2]: https://chromedevtools.github.io/devtools-protocol/tot/
|
||||
|
||||
[3.1]: #nodejs
|
||||
[3.2]: https://chromedevtools.github.io/devtools-protocol/v8/
|
||||
|
||||
[4.1]: #safari-ios
|
||||
[4.2]: http://trac.webkit.org/browser/trunk/Source/JavaScriptCore/inspector/protocol
|
||||
|
||||
[5.1]: #edge
|
||||
[5.2]: https://docs.microsoft.com/en-us/microsoft-edge/devtools-protocol/0.1/domains/
|
||||
|
||||
[6.1]: #firefox-nightly
|
||||
[6.2]: https://firefox-source-docs.mozilla.org/remote/index.html
|
||||
|
||||
[v6.3.0]: https://nodejs.org/en/blog/release/v6.3.0/
|
||||
|
||||
[Protocol]: #cdpprotocoloptions-callback
|
||||
[List]: #cdplistoptions-callback
|
||||
[New]: #cdpnewoptions-callback
|
||||
[Activate]: #cdpactivateoptions-callback
|
||||
[Close]: #cdpcloseoptions-callback
|
||||
[Version]: #cdpversionoptions-callback
|
||||
|
||||
The meaning of *target* varies according to the implementation, for example,
|
||||
each Chrome tab represents a target whereas for Node.js a target is the
|
||||
currently inspected script.
|
||||
|
||||
## Setup
|
||||
|
||||
An instance of either Chrome itself or another implementation needs to be
|
||||
running on a known port in order to use this module (defaults to
|
||||
`localhost:9222`).
|
||||
|
||||
### Chrome/Chromium
|
||||
|
||||
#### Desktop
|
||||
|
||||
Start Chrome with the `--remote-debugging-port` option, for example:
|
||||
|
||||
google-chrome --remote-debugging-port=9222
|
||||
|
||||
##### Headless
|
||||
|
||||
Since version 59, additionally use the `--headless` option, for example:
|
||||
|
||||
google-chrome --headless --remote-debugging-port=9222
|
||||
|
||||
#### Android
|
||||
|
||||
Plug the device and make sure to authorize the connection from the device itself. Then
|
||||
enable the port forwarding, for example:
|
||||
|
||||
adb -d forward tcp:9222 localabstract:chrome_devtools_remote
|
||||
|
||||
After that you should be able to use `http://127.0.0.1:9222` as usual, but note that in
|
||||
Android, Chrome does not have its own protocol available, so a local version must be used.
|
||||
See [here](#chrome-debugging-protocol-versions) for more information.
|
||||
|
||||
##### WebView
|
||||
|
||||
In order to be inspectable, a WebView must
|
||||
be [configured for debugging][webview] and the corresponding process ID must be
|
||||
known. There are several ways to obtain it, for example:
|
||||
|
||||
adb shell grep -a webview_devtools_remote /proc/net/unix
|
||||
|
||||
Finally, port forwarding can be enabled as follows:
|
||||
|
||||
adb forward tcp:9222 localabstract:webview_devtools_remote_<pid>
|
||||
|
||||
[webview]: https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews#configure_webviews_for_debugging
|
||||
|
||||
### Opera
|
||||
|
||||
Start Opera with the `--remote-debugging-port` option, for example:
|
||||
|
||||
opera --remote-debugging-port=9222
|
||||
|
||||
### Node.js
|
||||
|
||||
Start Node.js with the `--inspect` option, for example:
|
||||
|
||||
node --inspect=9222 script.js
|
||||
|
||||
### Safari (iOS)
|
||||
|
||||
Install and run the [iOS WebKit Debug Proxy][iwdp]. Then use it with the `local`
|
||||
option set to `true` to use the local version of the protocol or pass a custom
|
||||
descriptor upon connection (`protocol` option).
|
||||
|
||||
[iwdp]: https://github.com/google/ios-webkit-debug-proxy
|
||||
|
||||
### Edge
|
||||
|
||||
Start Edge with the `--devtools-server-port` option, for example:
|
||||
|
||||
MicrosoftEdge.exe --devtools-server-port 9222 about:blank
|
||||
|
||||
Please find more information [here][edge-devtools].
|
||||
|
||||
[edge-devtools]: https://docs.microsoft.com/en-us/microsoft-edge/devtools-protocol/
|
||||
|
||||
### Firefox (Nightly)
|
||||
|
||||
Start Firefox with the `--remote-debugging-port` option, for example:
|
||||
|
||||
firefox --remote-debugging-port 9222
|
||||
|
||||
Bear in mind that this is an experimental feature of Firefox.
|
||||
|
||||
## Bundled client
|
||||
|
||||
This module comes with a bundled client application that can be used to
|
||||
interactively control a remote instance.
|
||||
|
||||
### Target management
|
||||
|
||||
The bundled client exposes subcommands to interact with the HTTP frontend
|
||||
(e.g., [List](#cdplistoptions-callback), [New](#cdpnewoptions-callback), etc.),
|
||||
run with `--help` to display the list of available options.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
```js
|
||||
$ chrome-remote-interface new 'http://example.com'
|
||||
{
|
||||
"description": "",
|
||||
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/b049bb56-de7d-424c-a331-6ae44cf7ae01",
|
||||
"id": "b049bb56-de7d-424c-a331-6ae44cf7ae01",
|
||||
"thumbnailUrl": "/thumb/b049bb56-de7d-424c-a331-6ae44cf7ae01",
|
||||
"title": "",
|
||||
"type": "page",
|
||||
"url": "http://example.com/",
|
||||
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/b049bb56-de7d-424c-a331-6ae44cf7ae01"
|
||||
}
|
||||
$ chrome-remote-interface close 'b049bb56-de7d-424c-a331-6ae44cf7ae01'
|
||||
```
|
||||
|
||||
### Inspection
|
||||
|
||||
Using the `inspect` subcommand it is possible to perform [command execution](#clientdomainmethodparams-callback)
|
||||
and [event binding](#clientdomaineventcallback) in a REPL fashion that provides completion.
|
||||
|
||||
Here is a sample session:
|
||||
|
||||
```js
|
||||
$ chrome-remote-interface inspect
|
||||
>>> Runtime.evaluate({expression: 'window.location.toString()'})
|
||||
{ result: { type: 'string', value: 'about:blank' } }
|
||||
>>> Page.enable()
|
||||
{}
|
||||
>>> Page.loadEventFired(console.log)
|
||||
[Function]
|
||||
>>> Page.navigate({url: 'https://github.com'})
|
||||
{ frameId: 'E1657E22F06E6E0BE13DFA8130C20298',
|
||||
loaderId: '439236ADE39978F98C20E8939A32D3A5' }
|
||||
>>> { timestamp: 7454.721299 } // from Page.loadEventFired
|
||||
>>> Runtime.evaluate({expression: 'window.location.toString()'})
|
||||
{ result: { type: 'string', value: 'https://github.com/' } }
|
||||
```
|
||||
|
||||
Additionally there are some custom commands available:
|
||||
|
||||
```js
|
||||
>>> .help
|
||||
[...]
|
||||
.reset Remove all the registered event handlers
|
||||
.target Display the current target
|
||||
```
|
||||
|
||||
## Embedded documentation
|
||||
|
||||
In both the REPL and the regular API every object of the protocol is *decorated*
|
||||
with the meta information found within the descriptor. In addition The
|
||||
`category` field is added, which determines if the member is a `command`, an
|
||||
`event` or a `type`.
|
||||
|
||||
For example to learn how to call `Page.navigate`:
|
||||
|
||||
```js
|
||||
>>> Page.navigate
|
||||
{ [Function]
|
||||
category: 'command',
|
||||
parameters: { url: { type: 'string', description: 'URL to navigate the page to.' } },
|
||||
returns:
|
||||
[ { name: 'frameId',
|
||||
'$ref': 'FrameId',
|
||||
hidden: true,
|
||||
description: 'Frame id that will be navigated.' } ],
|
||||
description: 'Navigates current page to the given URL.',
|
||||
handlers: [ 'browser', 'renderer' ] }
|
||||
```
|
||||
|
||||
To learn about the parameters returned by the `Network.requestWillBeSent` event:
|
||||
|
||||
```js
|
||||
>>> Network.requestWillBeSent
|
||||
{ [Function]
|
||||
category: 'event',
|
||||
description: 'Fired when page is about to send HTTP request.',
|
||||
parameters:
|
||||
{ requestId: { '$ref': 'RequestId', description: 'Request identifier.' },
|
||||
frameId:
|
||||
{ '$ref': 'Page.FrameId',
|
||||
description: 'Frame identifier.',
|
||||
hidden: true },
|
||||
loaderId: { '$ref': 'LoaderId', description: 'Loader identifier.' },
|
||||
documentURL:
|
||||
{ type: 'string',
|
||||
description: 'URL of the document this request is loaded for.' },
|
||||
request: { '$ref': 'Request', description: 'Request data.' },
|
||||
timestamp: { '$ref': 'Timestamp', description: 'Timestamp.' },
|
||||
wallTime:
|
||||
{ '$ref': 'Timestamp',
|
||||
hidden: true,
|
||||
description: 'UTC Timestamp.' },
|
||||
initiator: { '$ref': 'Initiator', description: 'Request initiator.' },
|
||||
redirectResponse:
|
||||
{ optional: true,
|
||||
'$ref': 'Response',
|
||||
description: 'Redirect response data.' },
|
||||
type:
|
||||
{ '$ref': 'Page.ResourceType',
|
||||
optional: true,
|
||||
hidden: true,
|
||||
description: 'Type of this resource.' } } }
|
||||
```
|
||||
|
||||
To inspect the `Network.Request` (note that unlike commands and events, types
|
||||
are named in upper camel case) type:
|
||||
|
||||
```js
|
||||
>>> Network.Request
|
||||
{ category: 'type',
|
||||
id: 'Request',
|
||||
type: 'object',
|
||||
description: 'HTTP request data.',
|
||||
properties:
|
||||
{ url: { type: 'string', description: 'Request URL.' },
|
||||
method: { type: 'string', description: 'HTTP request method.' },
|
||||
headers: { '$ref': 'Headers', description: 'HTTP request headers.' },
|
||||
postData:
|
||||
{ type: 'string',
|
||||
optional: true,
|
||||
description: 'HTTP POST request data.' },
|
||||
mixedContentType:
|
||||
{ optional: true,
|
||||
type: 'string',
|
||||
enum: [Object],
|
||||
description: 'The mixed content status of the request, as defined in http://www.w3.org/TR/mixed-content/' },
|
||||
initialPriority:
|
||||
{ '$ref': 'ResourcePriority',
|
||||
description: 'Priority of the resource request at the time request is sent.' } } }
|
||||
```
|
||||
|
||||
## Chrome Debugging Protocol versions
|
||||
|
||||
By default `chrome-remote-interface` *asks* the remote instance to provide its
|
||||
own protocol.
|
||||
|
||||
This behavior can be changed by setting the `local` option to `true`
|
||||
upon [connection](#cdpoptions-callback), in which case the [local version] of
|
||||
the protocol descriptor is used. This file is manually updated from time to time
|
||||
using `scripts/update-protocol.sh` and pushed to this repository.
|
||||
|
||||
To further override the above behavior there are basically two options:
|
||||
|
||||
- pass a custom protocol descriptor upon [connection](#cdpoptions-callback)
|
||||
(`protocol` option);
|
||||
|
||||
- use the *raw* version of the [commands](#clientsendmethod-params-callback)
|
||||
and [events](#event-domainmethod) interface to use bleeding-edge features that
|
||||
do not appear in the [local version] of the protocol descriptor;
|
||||
|
||||
[local version]: lib/protocol.json
|
||||
|
||||
## Browser usage
|
||||
|
||||
This module is able to run within a web context, with obvious limitations
|
||||
though, namely external HTTP requests
|
||||
([List](#cdplistoptions-callback), [New](#cdpnewoptions-callback), etc.) cannot
|
||||
be performed directly, for this reason the user must provide a global
|
||||
`criRequest` in order to use them:
|
||||
|
||||
```js
|
||||
function criRequest(options, callback) {}
|
||||
```
|
||||
|
||||
`options` is the same object used by the Node.js `http` module and `callback` is
|
||||
a function taking two arguments: `err` (JavaScript `Error` object or `null`) and
|
||||
`data` (string result).
|
||||
|
||||
### Using [webpack](https://webpack.github.io/)
|
||||
|
||||
It just works, simply require this module:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
```
|
||||
|
||||
### Using *vanilla* JavaScript
|
||||
|
||||
To generate a JavaScript file that can be used with a `<script>` element:
|
||||
|
||||
1. run `npm install` from the root directory;
|
||||
|
||||
2. manually run webpack with:
|
||||
|
||||
TARGET=var npm run webpack
|
||||
|
||||
3. use as:
|
||||
|
||||
```html
|
||||
<script>
|
||||
function criRequest(options, callback) { /*...*/ }
|
||||
</script>
|
||||
<script src="chrome-remote-interface.js"></script>
|
||||
```
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
[TypeScript][] definitions are kindly provided by [Khairul Azhar Kasmiran][] and [Seth Westphal][], and can be installed from [DefinitelyTyped][]:
|
||||
|
||||
```
|
||||
npm install --save-dev @types/chrome-remote-interface
|
||||
```
|
||||
|
||||
Note that the TypeScript definitions are automatically generated from the npm package `devtools-protocol@0.0.927104`. For other versions of devtools-protocol:
|
||||
|
||||
1. Install patch-package using [the instructions given](https://github.com/ds300/patch-package#set-up).
|
||||
2. Copy the contents of the corresponding https://github.com/ChromeDevTools/devtools-protocol/tree/master/types folder (according to commit) into `node_modules/devtools-protocol/types`.
|
||||
3. Run `npx patch-package devtools-protocol` so that the changes persist across an `npm install`.
|
||||
|
||||
[TypeScript]: https://www.typescriptlang.org/
|
||||
[Khairul Azhar Kasmiran]: https://github.com/kazarmy
|
||||
[Seth Westphal]: https://github.com/westy92
|
||||
[DefinitelyTyped]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/chrome-remote-interface
|
||||
|
||||
## API
|
||||
|
||||
The API consists of three parts:
|
||||
|
||||
- *DevTools* methods (for those [implementations](#implementations) that support
|
||||
them, e.g., [List](#cdplistoptions-callback), [New](#cdpnewoptions-callback),
|
||||
etc.);
|
||||
|
||||
- [connection](#cdpoptions-callback) establishment;
|
||||
|
||||
- the actual [protocol interaction](#class-cdp).
|
||||
|
||||
### CDP([options], [callback])
|
||||
|
||||
Connects to a remote instance using the [Chrome Debugging Protocol].
|
||||
|
||||
`options` is an object with the following optional properties:
|
||||
|
||||
- `host`: HTTP frontend host. Defaults to `localhost`;
|
||||
- `port`: HTTP frontend port. Defaults to `9222`;
|
||||
- `secure`: HTTPS/WSS frontend. Defaults to `false`;
|
||||
- `useHostName`: do not perform a DNS lookup of the host. Defaults to `false`;
|
||||
- `alterPath`: a `function` taking and returning the path fragment of a URL
|
||||
before that a request happens. Defaults to the identity function;
|
||||
- `target`: determines which target this client should attach to. The behavior
|
||||
changes according to the type:
|
||||
|
||||
- a `function` that takes the array returned by the `List` method and returns
|
||||
a target or its numeric index relative to the array;
|
||||
- a target `object` like those returned by the `New` and `List` methods;
|
||||
- a `string` representing the raw WebSocket URL, in this case `host` and
|
||||
`port` are not used to fetch the target list, yet they are used to complete
|
||||
the URL if relative;
|
||||
- a `string` representing the target id.
|
||||
|
||||
Defaults to a function which returns the first available target according to
|
||||
the implementation (note that at most one connection can be established to the
|
||||
same target);
|
||||
- `protocol`: [Chrome Debugging Protocol] descriptor object. Defaults to use the
|
||||
protocol chosen according to the `local` option;
|
||||
- `local`: a boolean indicating whether the protocol must be fetched *remotely*
|
||||
or if the local version must be used. It has no effect if the `protocol`
|
||||
option is set. Defaults to `false`.
|
||||
|
||||
These options are also valid properties of all the instances of the `CDP`
|
||||
class. In addition to that, the `webSocketUrl` field contains the currently used
|
||||
WebSocket URL.
|
||||
|
||||
`callback` is a listener automatically added to the `connect` event of the
|
||||
returned `EventEmitter`. When `callback` is omitted a `Promise` object is
|
||||
returned which becomes fulfilled if the `connect` event is triggered and
|
||||
rejected if the `error` event is triggered.
|
||||
|
||||
The `EventEmitter` supports the following events:
|
||||
|
||||
#### Event: 'connect'
|
||||
|
||||
```js
|
||||
function (client) {}
|
||||
```
|
||||
|
||||
Emitted when the connection to the WebSocket is established.
|
||||
|
||||
`client` is an instance of the `CDP` class.
|
||||
|
||||
#### Event: 'error'
|
||||
|
||||
```js
|
||||
function (err) {}
|
||||
```
|
||||
|
||||
Emitted when `http://host:port/json` cannot be reached or if it is not possible
|
||||
to connect to the WebSocket.
|
||||
|
||||
`err` is an instance of `Error`.
|
||||
|
||||
### CDP.Protocol([options], [callback])
|
||||
|
||||
Fetch the [Chrome Debugging Protocol] descriptor.
|
||||
|
||||
`options` is an object with the following optional properties:
|
||||
|
||||
- `host`: HTTP frontend host. Defaults to `localhost`;
|
||||
- `port`: HTTP frontend port. Defaults to `9222`;
|
||||
- `secure`: HTTPS/WSS frontend. Defaults to `false`;
|
||||
- `useHostName`: do not perform a DNS lookup of the host. Defaults to `false`;
|
||||
- `alterPath`: a `function` taking and returning the path fragment of a URL
|
||||
before that a request happens. Defaults to the identity function;
|
||||
- `local`: a boolean indicating whether the protocol must be fetched *remotely*
|
||||
or if the local version must be returned. Defaults to `false`.
|
||||
|
||||
`callback` is executed when the protocol is fetched, it gets the following
|
||||
arguments:
|
||||
|
||||
- `err`: a `Error` object indicating the success status;
|
||||
- `protocol`: the [Chrome Debugging Protocol] descriptor.
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
CDP.Protocol((err, protocol) => {
|
||||
if (!err) {
|
||||
console.log(JSON.stringify(protocol, null, 4));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### CDP.List([options], [callback])
|
||||
|
||||
Request the list of the available open targets/tabs of the remote instance.
|
||||
|
||||
`options` is an object with the following optional properties:
|
||||
|
||||
- `host`: HTTP frontend host. Defaults to `localhost`;
|
||||
- `port`: HTTP frontend port. Defaults to `9222`;
|
||||
- `secure`: HTTPS/WSS frontend. Defaults to `false`;
|
||||
- `useHostName`: do not perform a DNS lookup of the host. Defaults to `false`;
|
||||
- `alterPath`: a `function` taking and returning the path fragment of a URL
|
||||
before that a request happens. Defaults to the identity function.
|
||||
|
||||
`callback` is executed when the list is correctly received, it gets the
|
||||
following arguments:
|
||||
|
||||
- `err`: a `Error` object indicating the success status;
|
||||
- `targets`: the array returned by `http://host:port/json/list` containing the
|
||||
target list.
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
CDP.List((err, targets) => {
|
||||
if (!err) {
|
||||
console.log(targets);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### CDP.New([options], [callback])
|
||||
|
||||
Create a new target/tab in the remote instance.
|
||||
|
||||
`options` is an object with the following optional properties:
|
||||
|
||||
- `host`: HTTP frontend host. Defaults to `localhost`;
|
||||
- `port`: HTTP frontend port. Defaults to `9222`;
|
||||
- `secure`: HTTPS/WSS frontend. Defaults to `false`;
|
||||
- `useHostName`: do not perform a DNS lookup of the host. Defaults to `false`;
|
||||
- `alterPath`: a `function` taking and returning the path fragment of a URL
|
||||
before that a request happens. Defaults to the identity function;
|
||||
- `url`: URL to load in the new target/tab. Defaults to `about:blank`.
|
||||
|
||||
`callback` is executed when the target is created, it gets the following
|
||||
arguments:
|
||||
|
||||
- `err`: a `Error` object indicating the success status;
|
||||
- `target`: the object returned by `http://host:port/json/new` containing the
|
||||
target.
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
CDP.New((err, target) => {
|
||||
if (!err) {
|
||||
console.log(target);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### CDP.Activate([options], [callback])
|
||||
|
||||
Activate an open target/tab of the remote instance.
|
||||
|
||||
`options` is an object with the following properties:
|
||||
|
||||
- `host`: HTTP frontend host. Defaults to `localhost`;
|
||||
- `port`: HTTP frontend port. Defaults to `9222`;
|
||||
- `secure`: HTTPS/WSS frontend. Defaults to `false`;
|
||||
- `useHostName`: do not perform a DNS lookup of the host. Defaults to `false`;
|
||||
- `alterPath`: a `function` taking and returning the path fragment of a URL
|
||||
before that a request happens. Defaults to the identity function;
|
||||
- `id`: Target id. Required, no default.
|
||||
|
||||
`callback` is executed when the response to the activation request is
|
||||
received. It gets the following arguments:
|
||||
|
||||
- `err`: a `Error` object indicating the success status;
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
CDP.Activate({id: 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, (err) => {
|
||||
if (!err) {
|
||||
console.log('target is activated');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### CDP.Close([options], [callback])
|
||||
|
||||
Close an open target/tab of the remote instance.
|
||||
|
||||
`options` is an object with the following properties:
|
||||
|
||||
- `host`: HTTP frontend host. Defaults to `localhost`;
|
||||
- `port`: HTTP frontend port. Defaults to `9222`;
|
||||
- `secure`: HTTPS/WSS frontend. Defaults to `false`;
|
||||
- `useHostName`: do not perform a DNS lookup of the host. Defaults to `false`;
|
||||
- `alterPath`: a `function` taking and returning the path fragment of a URL
|
||||
before that a request happens. Defaults to the identity function;
|
||||
- `id`: Target id. Required, no default.
|
||||
|
||||
`callback` is executed when the response to the close request is received. It
|
||||
gets the following arguments:
|
||||
|
||||
- `err`: a `Error` object indicating the success status;
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
CDP.Close({id: 'CC46FBFA-3BDA-493B-B2E4-2BE6EB0D97EC'}, (err) => {
|
||||
if (!err) {
|
||||
console.log('target is closing');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that the callback is fired when the target is *queued* for removal, but the
|
||||
actual removal will occur asynchronously.
|
||||
|
||||
### CDP.Version([options], [callback])
|
||||
|
||||
Request version information from the remote instance.
|
||||
|
||||
`options` is an object with the following optional properties:
|
||||
|
||||
- `host`: HTTP frontend host. Defaults to `localhost`;
|
||||
- `port`: HTTP frontend port. Defaults to `9222`;
|
||||
- `secure`: HTTPS/WSS frontend. Defaults to `false`;
|
||||
- `useHostName`: do not perform a DNS lookup of the host. Defaults to `false`;
|
||||
- `alterPath`: a `function` taking and returning the path fragment of a URL
|
||||
before that a request happens. Defaults to the identity function.
|
||||
|
||||
`callback` is executed when the version information is correctly received, it
|
||||
gets the following arguments:
|
||||
|
||||
- `err`: a `Error` object indicating the success status;
|
||||
- `info`: a JSON object returned by `http://host:port/json/version` containing
|
||||
the version information.
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const CDP = require('chrome-remote-interface');
|
||||
CDP.Version((err, info) => {
|
||||
if (!err) {
|
||||
console.log(info);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Class: CDP
|
||||
|
||||
#### Event: 'event'
|
||||
|
||||
```js
|
||||
function (message) {}
|
||||
```
|
||||
|
||||
Emitted when the remote instance sends any notification through the WebSocket.
|
||||
|
||||
`message` is the object received, it has the following properties:
|
||||
|
||||
- `method`: a string describing the notification (e.g.,
|
||||
`'Network.requestWillBeSent'`);
|
||||
- `params`: an object containing the payload;
|
||||
- `sessionId`: an optional string representing the session identifier.
|
||||
|
||||
Refer to the [Chrome Debugging Protocol] specification for more information.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
client.on('event', (message) => {
|
||||
if (message.method === 'Network.requestWillBeSent') {
|
||||
console.log(message.params);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Event: '`<domain>`.`<method>`'
|
||||
|
||||
```js
|
||||
function (params, sessionId) {}
|
||||
```
|
||||
|
||||
Emitted when the remote instance sends a notification for `<domain>.<method>`
|
||||
through the WebSocket.
|
||||
|
||||
`params` is an object containing the payload.
|
||||
|
||||
`sessionId` is an optional string representing the session identifier.
|
||||
|
||||
This is just a utility event which allows to easily listen for specific
|
||||
notifications (see [`'event'`](#event-event)), for example:
|
||||
|
||||
```js
|
||||
client.on('Network.requestWillBeSent', console.log);
|
||||
```
|
||||
|
||||
Additionally, the equivalent `<domain>.on('<method>', ...)` syntax is available, for example:
|
||||
|
||||
```js
|
||||
client.Network.on('requestWillBeSent', console.log);
|
||||
```
|
||||
|
||||
#### Event: '`<domain>`.`<method>`.`<sessionId>`'
|
||||
|
||||
```js
|
||||
function (params, sessionId) {}
|
||||
```
|
||||
|
||||
Equivalent to the following but only for those events belonging to the given `session`:
|
||||
|
||||
```js
|
||||
client.on('<domain>.<event>', callback);
|
||||
```
|
||||
|
||||
#### Event: 'ready'
|
||||
|
||||
```js
|
||||
function () {}
|
||||
```
|
||||
|
||||
Emitted every time that there are no more pending commands waiting for a
|
||||
response from the remote instance. The interaction is asynchronous so the only
|
||||
way to serialize a sequence of commands is to use the callback provided by
|
||||
the [`send`](#clientsendmethod-params-callback) method. This event acts as a
|
||||
barrier and it is useful to avoid the *callback hell* in certain simple
|
||||
situations.
|
||||
|
||||
Users are encouraged to extensively check the response of each method and should
|
||||
prefer the promises API when dealing with complex asynchronous program flows.
|
||||
|
||||
For example to load a URL only after having enabled the notifications of both
|
||||
`Network` and `Page` domains:
|
||||
|
||||
```js
|
||||
client.Network.enable();
|
||||
client.Page.enable();
|
||||
client.once('ready', () => {
|
||||
client.Page.navigate({url: 'https://github.com'});
|
||||
});
|
||||
```
|
||||
|
||||
In this particular case, not enforcing this kind of serialization may cause that
|
||||
the remote instance does not properly deliver the desired notifications the
|
||||
client.
|
||||
|
||||
|
||||
#### Event: 'disconnect'
|
||||
|
||||
```js
|
||||
function () {}
|
||||
```
|
||||
|
||||
Emitted when the instance closes the WebSocket connection.
|
||||
|
||||
This may happen for example when the user opens DevTools or when the tab is
|
||||
closed.
|
||||
|
||||
#### client.send(method, [params], [sessionId], [callback])
|
||||
|
||||
Issue a command to the remote instance.
|
||||
|
||||
`method` is a string describing the command.
|
||||
|
||||
`params` is an object containing the payload.
|
||||
|
||||
`sessionId` is a string representing the session identifier.
|
||||
|
||||
`callback` is executed when the remote instance sends a response to this
|
||||
command, it gets the following arguments:
|
||||
|
||||
- `error`: a boolean value indicating the success status, as reported by the
|
||||
remote instance;
|
||||
- `response`: an object containing either the response (`result` field, if
|
||||
`error === false`) or the indication of the error (`error` field, if `error
|
||||
=== true`).
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned instead, with the
|
||||
fulfilled/rejected states implemented according to the `error` parameter. The
|
||||
`Error` object con be an instance of [`ProtocolError`](#cdpprotocolerror) for
|
||||
protocol invocation errors. Alternatively, in case of low-level WebSocket
|
||||
errors, the `error` parameter contains the originating `Error` object.
|
||||
|
||||
Note that the field `id` mentioned in the [Chrome Debugging Protocol]
|
||||
specification is managed internally and it is not exposed to the user.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
client.send('Page.navigate', {url: 'https://github.com'}, console.log);
|
||||
```
|
||||
|
||||
#### client.`<domain>`.`<method>`([params], [sessionId], [callback])
|
||||
|
||||
Just a shorthand for:
|
||||
|
||||
```js
|
||||
client.send('<domain>.<method>', params, sessionId, callback);
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
client.Page.navigate({url: 'https://github.com'}, console.log);
|
||||
```
|
||||
|
||||
#### client.`<domain>`.`<event>`([sessionId], [callback])
|
||||
|
||||
Just a shorthand for:
|
||||
|
||||
```js
|
||||
client.on('<domain>.<event>[.<sessionId>]', callback);
|
||||
```
|
||||
|
||||
When `callback` is omitted the event is registered only once and a `Promise`
|
||||
object is returned. Notice though that in this case the optional `sessionId` usually passed to `callback` is not returned.
|
||||
|
||||
When `callback` is provided, it returns a function that can be used to
|
||||
unsubscribe `callback` from the event, it can be useful when anonymous functions
|
||||
are used as callbacks.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const unsubscribe = client.Network.requestWillBeSent((params, sessionId) => {
|
||||
console.log(params.request.url);
|
||||
});
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
#### client.close([callback])
|
||||
|
||||
Close the connection to the remote instance.
|
||||
|
||||
`callback` is executed when the WebSocket is successfully closed.
|
||||
|
||||
When `callback` is omitted a `Promise` object is returned.
|
||||
|
||||
#### client['`<domain>`.`<name>`']
|
||||
|
||||
Just a shorthand for:
|
||||
|
||||
```js
|
||||
client.<domain>.<name>
|
||||
```
|
||||
|
||||
Where `<name>` can be a command, an event, or a type.
|
||||
|
||||
### CDP.ProtocolError
|
||||
|
||||
Error returned by the [`send`](#clientsendmethod-params-callback) method in case Chrome experienced issues in the protocol invocation. It exposes the following fields:
|
||||
|
||||
- `request`: the raw request object containing the `method`, `params`, and `sessionId` fields;
|
||||
- `response`: the raw response from Chrome, usually containing the `code`, `message`, and `data` fields.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Invoking `Domain.methodOrEvent` I obtain `Domain.methodOrEvent is not a function`
|
||||
|
||||
This means that you are trying to use a method or an event that are not present
|
||||
in the protocol descriptor that you are using.
|
||||
|
||||
If the protocol is fetched from Chrome directly, then it means that this version
|
||||
of Chrome does not support that feature. The solution is to update it.
|
||||
|
||||
If you are using a local or custom version of the protocol, then it means that
|
||||
the version is obsolete. The solution is to provide an up-to-date one, or if you
|
||||
are using the protocol embedded in chrome-remote-interface, make sure to be
|
||||
running the latest version of this module. In case the embedded protocol is
|
||||
obsolete, please [file an issue](https://github.com/cyrus-and/chrome-remote-interface/issues/new).
|
||||
|
||||
See [here](#chrome-debugging-protocol-versions) for more information.
|
||||
|
||||
### Invoking `Domain.method` I obtain `Domain.method wasn't found`
|
||||
|
||||
This means that you are providing a custom or local protocol descriptor
|
||||
(`CDP({protocol: customProtocol})`) which declares `Domain.method` while the
|
||||
Chrome version that you are using does not support it.
|
||||
|
||||
To inspect the currently available protocol descriptor use:
|
||||
|
||||
```
|
||||
$ chrome-remote-interface inspect
|
||||
```
|
||||
|
||||
See [here](#chrome-debugging-protocol-versions) for more information.
|
||||
|
||||
### Why my program stalls or behave unexpectedly if I run Chrome in a Docker container?
|
||||
|
||||
This happens because the size of `/dev/shm` is set to 64MB by default in Docker
|
||||
and may not be enough for Chrome to navigate certain web pages.
|
||||
|
||||
You can change this value by running your container with, say,
|
||||
`--shm-size=256m`.
|
||||
|
||||
### Using `Runtime.evaluate` with `awaitPromise: true` I sometimes obtain `Error: Promise was collected`
|
||||
|
||||
This is thrown by `Runtime.evaluate` when the browser-side promise gets
|
||||
*collected* by the Chrome's garbage collector, this happens when the whole
|
||||
JavaScript execution environment is invalidated, e.g., a when page is navigated
|
||||
or reloaded while a promise is still waiting to be resolved.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```
|
||||
$ chrome-remote-interface inspect
|
||||
>>> Runtime.evaluate({expression: `new Promise(() => {})`, awaitPromise: true})
|
||||
>>> Page.reload() // then wait several seconds
|
||||
{ result: {} }
|
||||
{ error: { code: -32000, message: 'Promise was collected' } }
|
||||
```
|
||||
|
||||
To fix this, just make sure there are no pending promises before closing,
|
||||
reloading, etc. a page.
|
||||
|
||||
### How does this compare to Puppeteer?
|
||||
|
||||
[Puppeteer] is an additional high-level API built upon the [Chrome Debugging
|
||||
Protocol] which, among the other things, may start and use a bundled version of
|
||||
Chromium instead of the one installed on your system. Use it if its API meets
|
||||
your needs as it would probably be easier to work with.
|
||||
|
||||
chrome-remote-interface instead is just a general purpose 1:1 Node.js binding
|
||||
for the [Chrome Debugging Protocol]. Use it if you need all the power of the raw
|
||||
protocol, e.g., to implement your own high-level API.
|
||||
|
||||
See [#240] for a more thorough discussion.
|
||||
|
||||
[Puppeteer]: https://github.com/GoogleChrome/puppeteer
|
||||
[#240]: https://github.com/cyrus-and/chrome-remote-interface/issues/240
|
||||
|
||||
## Contributors
|
||||
|
||||
- [Andrey Sidorov](https://github.com/sidorares)
|
||||
- [Greg Cochard](https://github.com/gcochard)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Chrome Debugging Protocol]
|
||||
- [Chrome Debugging Protocol Google group](https://groups.google.com/forum/#!forum/chrome-debugging-protocol)
|
||||
- [devtools-protocol official repo](https://github.com/ChromeDevTools/devtools-protocol)
|
||||
- [Showcase Chrome Debugging Protocol Clients](https://developer.chrome.com/devtools/docs/debugging-clients)
|
||||
- [Awesome chrome-devtools](https://github.com/ChromeDevTools/awesome-chrome-devtools)
|
||||
|
||||
[Chrome Debugging Protocol]: https://chromedevtools.github.io/devtools-protocol/
|
||||
311
node_modules/chrome-remote-interface/bin/client.js
generated
vendored
Executable file
311
node_modules/chrome-remote-interface/bin/client.js
generated
vendored
Executable file
@@ -0,0 +1,311 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const repl = require('repl');
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const program = require('commander');
|
||||
|
||||
const CDP = require('../');
|
||||
const packageInfo = require('../package.json');
|
||||
|
||||
function display(object) {
|
||||
return util.inspect(object, {
|
||||
colors: process.stdout.isTTY,
|
||||
depth: null
|
||||
});
|
||||
}
|
||||
|
||||
function toJSON(object) {
|
||||
return JSON.stringify(object, null, 4);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
function inspect(target, args, options) {
|
||||
options.local = args.local;
|
||||
// otherwise the active target
|
||||
if (target) {
|
||||
if (args.webSocket) {
|
||||
// by WebSocket URL
|
||||
options.target = target;
|
||||
} else {
|
||||
// by target id
|
||||
options.target = (targets) => {
|
||||
return targets.findIndex((_target) => {
|
||||
return _target.id === target;
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (args.protocol) {
|
||||
options.protocol = JSON.parse(fs.readFileSync(args.protocol));
|
||||
}
|
||||
|
||||
CDP(options, (client) => {
|
||||
const cdpRepl = repl.start({
|
||||
prompt: process.stdin.isTTY ? '\x1b[32m>>>\x1b[0m ' : '',
|
||||
ignoreUndefined: true,
|
||||
writer: display
|
||||
});
|
||||
|
||||
// XXX always await promises on the REPL
|
||||
const defaultEval = cdpRepl.eval;
|
||||
cdpRepl.eval = (cmd, context, filename, callback) => {
|
||||
defaultEval(cmd, context, filename, async (err, result) => {
|
||||
if (err) {
|
||||
// propagate errors from the eval
|
||||
callback(err);
|
||||
} else {
|
||||
// awaits the promise and either return result or error
|
||||
try {
|
||||
callback(null, await Promise.resolve(result));
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const homePath = process.env.HOME || process.env.USERPROFILE;
|
||||
const historyFile = path.join(homePath, '.cri_history');
|
||||
const historySize = 10000;
|
||||
|
||||
function loadHistory() {
|
||||
// only if run from a terminal
|
||||
if (!process.stdin.isTTY) {
|
||||
return;
|
||||
}
|
||||
// attempt to open the history file
|
||||
let fd;
|
||||
try {
|
||||
fd = fs.openSync(historyFile, 'r');
|
||||
} catch (err) {
|
||||
return; // no history file present
|
||||
}
|
||||
// populate the REPL history
|
||||
fs.readFileSync(fd, 'utf8')
|
||||
.split('\n')
|
||||
.filter((entry) => {
|
||||
return entry.trim();
|
||||
})
|
||||
.reverse() // to be compatible with repl.history files
|
||||
.forEach((entry) => {
|
||||
cdpRepl.history.push(entry);
|
||||
});
|
||||
}
|
||||
|
||||
function saveHistory() {
|
||||
// only if run from a terminal
|
||||
if (!process.stdin.isTTY) {
|
||||
return;
|
||||
}
|
||||
// only store the last chunk
|
||||
const entries = cdpRepl.history.slice(0, historySize).reverse().join('\n');
|
||||
fs.writeFileSync(historyFile, entries + '\n');
|
||||
}
|
||||
|
||||
// utility custom command
|
||||
cdpRepl.defineCommand('target', {
|
||||
help: 'Display the current target',
|
||||
action: () => {
|
||||
console.log(client.webSocketUrl);
|
||||
cdpRepl.displayPrompt();
|
||||
}
|
||||
});
|
||||
|
||||
// utility to purge all the event handlers
|
||||
cdpRepl.defineCommand('reset', {
|
||||
help: 'Remove all the registered event handlers',
|
||||
action: () => {
|
||||
client.removeAllListeners();
|
||||
cdpRepl.displayPrompt();
|
||||
}
|
||||
});
|
||||
|
||||
// enable history
|
||||
loadHistory();
|
||||
|
||||
// disconnect on exit
|
||||
cdpRepl.on('exit', () => {
|
||||
if (process.stdin.isTTY) {
|
||||
console.log();
|
||||
}
|
||||
client.close();
|
||||
saveHistory();
|
||||
});
|
||||
|
||||
// exit on disconnection
|
||||
client.on('disconnect', () => {
|
||||
console.error('Disconnected.');
|
||||
saveHistory();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// add protocol API
|
||||
for (const domainObject of client.protocol.domains) {
|
||||
// walk the domain names
|
||||
const domainName = domainObject.domain;
|
||||
cdpRepl.context[domainName] = {};
|
||||
// walk the items in the domain
|
||||
for (const itemName in client[domainName]) {
|
||||
// add CDP object to the REPL context
|
||||
const cdpObject = client[domainName][itemName];
|
||||
cdpRepl.context[domainName][itemName] = cdpObject;
|
||||
}
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
console.error('Cannot connect to remote endpoint:', err.toString());
|
||||
});
|
||||
}
|
||||
|
||||
function list(options) {
|
||||
CDP.List(options, (err, targets) => {
|
||||
if (err) {
|
||||
console.error(err.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(toJSON(targets));
|
||||
});
|
||||
}
|
||||
|
||||
function _new(url, options) {
|
||||
options.url = url;
|
||||
CDP.New(options, (err, target) => {
|
||||
if (err) {
|
||||
console.error(err.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(toJSON(target));
|
||||
});
|
||||
}
|
||||
|
||||
function activate(args, options) {
|
||||
options.id = args;
|
||||
CDP.Activate(options, (err) => {
|
||||
if (err) {
|
||||
console.error(err.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function close(args, options) {
|
||||
options.id = args;
|
||||
CDP.Close(options, (err) => {
|
||||
if (err) {
|
||||
console.error(err.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function version(options) {
|
||||
CDP.Version(options, (err, info) => {
|
||||
if (err) {
|
||||
console.error(err.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(toJSON(info));
|
||||
});
|
||||
}
|
||||
|
||||
function protocol(args, options) {
|
||||
options.local = args.local;
|
||||
CDP.Protocol(options, (err, protocol) => {
|
||||
if (err) {
|
||||
console.error(err.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(toJSON(protocol));
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
let action;
|
||||
|
||||
program
|
||||
.option('-v, --v', 'Show this module version')
|
||||
.option('-t, --host <host>', 'HTTP frontend host')
|
||||
.option('-p, --port <port>', 'HTTP frontend port')
|
||||
.option('-s, --secure', 'HTTPS/WSS frontend')
|
||||
.option('-n, --use-host-name', 'Do not perform a DNS lookup of the host');
|
||||
|
||||
program
|
||||
.command('inspect [<target>]')
|
||||
.description('inspect a target (defaults to the first available target)')
|
||||
.option('-w, --web-socket', 'interpret <target> as a WebSocket URL instead of a target id')
|
||||
.option('-j, --protocol <file.json>', 'Chrome Debugging Protocol descriptor (overrides `--local`)')
|
||||
.option('-l, --local', 'Use the local protocol descriptor')
|
||||
.action((target, args) => {
|
||||
action = inspect.bind(null, target, args);
|
||||
});
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('list all the available targets/tabs')
|
||||
.action(() => {
|
||||
action = list;
|
||||
});
|
||||
|
||||
program
|
||||
.command('new [<url>]')
|
||||
.description('create a new target/tab')
|
||||
.action((url) => {
|
||||
action = _new.bind(null, url);
|
||||
});
|
||||
|
||||
program
|
||||
.command('activate <id>')
|
||||
.description('activate a target/tab by id')
|
||||
.action((id) => {
|
||||
action = activate.bind(null, id);
|
||||
});
|
||||
|
||||
program
|
||||
.command('close <id>')
|
||||
.description('close a target/tab by id')
|
||||
.action((id) => {
|
||||
action = close.bind(null, id);
|
||||
});
|
||||
|
||||
program
|
||||
.command('version')
|
||||
.description('show the browser version')
|
||||
.action(() => {
|
||||
action = version;
|
||||
});
|
||||
|
||||
program
|
||||
.command('protocol')
|
||||
.description('show the currently available protocol descriptor')
|
||||
.option('-l, --local', 'Return the local protocol descriptor')
|
||||
.action((args) => {
|
||||
action = protocol.bind(null, args);
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
// common options
|
||||
const options = {
|
||||
host: program.host,
|
||||
port: program.port,
|
||||
secure: program.secure,
|
||||
useHostName: program.useHostName
|
||||
};
|
||||
|
||||
if (action) {
|
||||
action(options);
|
||||
} else {
|
||||
if (program.v) {
|
||||
console.log(packageInfo.version);
|
||||
} else {
|
||||
program.outputHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
1
node_modules/chrome-remote-interface/chrome-remote-interface.js
generated
vendored
Normal file
1
node_modules/chrome-remote-interface/chrome-remote-interface.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
46
node_modules/chrome-remote-interface/index.js
generated
vendored
Normal file
46
node_modules/chrome-remote-interface/index.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const dns = require('dns');
|
||||
|
||||
const devtools = require('./lib/devtools.js');
|
||||
const errors = require('./lib/errors.js');
|
||||
const Chrome = require('./lib/chrome.js');
|
||||
|
||||
// XXX reset the default that has been changed in
|
||||
// (https://github.com/nodejs/node/pull/39987) to prefer IPv4. since
|
||||
// implementations alway bind on 127.0.0.1 this solution should be fairly safe
|
||||
// (see #467)
|
||||
if (dns.setDefaultResultOrder) {
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
}
|
||||
|
||||
function CDP(options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = undefined;
|
||||
}
|
||||
const notifier = new EventEmitter();
|
||||
if (typeof callback === 'function') {
|
||||
// allow to register the error callback later
|
||||
process.nextTick(() => {
|
||||
new Chrome(options, notifier);
|
||||
});
|
||||
return notifier.once('connect', callback);
|
||||
} else {
|
||||
return new Promise((fulfill, reject) => {
|
||||
notifier.once('connect', fulfill);
|
||||
notifier.once('error', reject);
|
||||
new Chrome(options, notifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CDP;
|
||||
module.exports.Protocol = devtools.Protocol;
|
||||
module.exports.List = devtools.List;
|
||||
module.exports.New = devtools.New;
|
||||
module.exports.Activate = devtools.Activate;
|
||||
module.exports.Close = devtools.Close;
|
||||
module.exports.Version = devtools.Version;
|
||||
module.exports.ProtocolError = errors.ProtocolError;
|
||||
92
node_modules/chrome-remote-interface/lib/api.js
generated
vendored
Normal file
92
node_modules/chrome-remote-interface/lib/api.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
function arrayToObject(parameters) {
|
||||
const keyValue = {};
|
||||
parameters.forEach((parameter) =>{
|
||||
const name = parameter.name;
|
||||
delete parameter.name;
|
||||
keyValue[name] = parameter;
|
||||
});
|
||||
return keyValue;
|
||||
}
|
||||
|
||||
function decorate(to, category, object) {
|
||||
to.category = category;
|
||||
Object.keys(object).forEach((field) => {
|
||||
// skip the 'name' field as it is part of the function prototype
|
||||
if (field === 'name') {
|
||||
return;
|
||||
}
|
||||
// commands and events have parameters whereas types have properties
|
||||
if (category === 'type' && field === 'properties' ||
|
||||
field === 'parameters') {
|
||||
to[field] = arrayToObject(object[field]);
|
||||
} else {
|
||||
to[field] = object[field];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addCommand(chrome, domainName, command) {
|
||||
const commandName = `${domainName}.${command.name}`;
|
||||
const handler = (params, sessionId, callback) => {
|
||||
return chrome.send(commandName, params, sessionId, callback);
|
||||
};
|
||||
decorate(handler, 'command', command);
|
||||
chrome[commandName] = chrome[domainName][command.name] = handler;
|
||||
}
|
||||
|
||||
function addEvent(chrome, domainName, event) {
|
||||
const eventName = `${domainName}.${event.name}`;
|
||||
const handler = (sessionId, handler) => {
|
||||
if (typeof sessionId === 'function') {
|
||||
handler = sessionId;
|
||||
sessionId = undefined;
|
||||
}
|
||||
const rawEventName = sessionId ? `${eventName}.${sessionId}` : eventName;
|
||||
if (typeof handler === 'function') {
|
||||
chrome.on(rawEventName, handler);
|
||||
return () => chrome.removeListener(rawEventName, handler);
|
||||
} else {
|
||||
return new Promise((fulfill, reject) => {
|
||||
chrome.once(rawEventName, fulfill);
|
||||
});
|
||||
}
|
||||
};
|
||||
decorate(handler, 'event', event);
|
||||
chrome[eventName] = chrome[domainName][event.name] = handler;
|
||||
}
|
||||
|
||||
function addType(chrome, domainName, type) {
|
||||
const typeName = `${domainName}.${type.id}`;
|
||||
const help = {};
|
||||
decorate(help, 'type', type);
|
||||
chrome[typeName] = chrome[domainName][type.id] = help;
|
||||
}
|
||||
|
||||
function prepare(object, protocol) {
|
||||
// assign the protocol and generate the shorthands
|
||||
object.protocol = protocol;
|
||||
protocol.domains.forEach((domain) => {
|
||||
const domainName = domain.domain;
|
||||
object[domainName] = {};
|
||||
// add commands
|
||||
(domain.commands || []).forEach((command) => {
|
||||
addCommand(object, domainName, command);
|
||||
});
|
||||
// add events
|
||||
(domain.events || []).forEach((event) => {
|
||||
addEvent(object, domainName, event);
|
||||
});
|
||||
// add types
|
||||
(domain.types || []).forEach((type) => {
|
||||
addType(object, domainName, type);
|
||||
});
|
||||
// add utility listener for each domain
|
||||
object[domainName].on = (eventName, handler) => {
|
||||
return object[domainName][eventName](handler);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.prepare = prepare;
|
||||
302
node_modules/chrome-remote-interface/lib/chrome.js
generated
vendored
Normal file
302
node_modules/chrome-remote-interface/lib/chrome.js
generated
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const util = require('util');
|
||||
const formatUrl = require('url').format;
|
||||
const parseUrl = require('url').parse;
|
||||
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const api = require('./api.js');
|
||||
const defaults = require('./defaults.js');
|
||||
const devtools = require('./devtools.js');
|
||||
const errors = require('./errors.js');
|
||||
|
||||
class Chrome extends EventEmitter {
|
||||
constructor(options, notifier) {
|
||||
super();
|
||||
// options
|
||||
const defaultTarget = (targets) => {
|
||||
// prefer type = 'page' inspectable targets as they represents
|
||||
// browser tabs (fall back to the first inspectable target
|
||||
// otherwise)
|
||||
let backup;
|
||||
let target = targets.find((target) => {
|
||||
if (target.webSocketDebuggerUrl) {
|
||||
backup = backup || target;
|
||||
return target.type === 'page';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
target = target || backup;
|
||||
if (target) {
|
||||
return target;
|
||||
} else {
|
||||
throw new Error('No inspectable targets');
|
||||
}
|
||||
};
|
||||
options = options || {};
|
||||
this.host = options.host || defaults.HOST;
|
||||
this.port = options.port || defaults.PORT;
|
||||
this.secure = !!(options.secure);
|
||||
this.useHostName = !!(options.useHostName);
|
||||
this.alterPath = options.alterPath || ((path) => path);
|
||||
this.protocol = options.protocol;
|
||||
this.local = !!(options.local);
|
||||
this.target = options.target || defaultTarget;
|
||||
// locals
|
||||
this._notifier = notifier;
|
||||
this._callbacks = {};
|
||||
this._nextCommandId = 1;
|
||||
// properties
|
||||
this.webSocketUrl = undefined;
|
||||
// operations
|
||||
this._start();
|
||||
}
|
||||
|
||||
// avoid misinterpreting protocol's members as custom util.inspect functions
|
||||
inspect(depth, options) {
|
||||
options.customInspect = false;
|
||||
return util.inspect(this, options);
|
||||
}
|
||||
|
||||
send(method, params, sessionId, callback) {
|
||||
// handle optional arguments
|
||||
const optionals = Array.from(arguments).slice(1);
|
||||
params = optionals.find(x => typeof x === 'object');
|
||||
sessionId = optionals.find(x => typeof x === 'string');
|
||||
callback = optionals.find(x => typeof x === 'function');
|
||||
// return a promise when a callback is not provided
|
||||
if (typeof callback === 'function') {
|
||||
this._enqueueCommand(method, params, sessionId, callback);
|
||||
return undefined;
|
||||
} else {
|
||||
return new Promise((fulfill, reject) => {
|
||||
this._enqueueCommand(method, params, sessionId, (error, response) => {
|
||||
if (error) {
|
||||
const request = {method, params, sessionId};
|
||||
reject(
|
||||
error instanceof Error
|
||||
? error // low-level WebSocket error
|
||||
: new errors.ProtocolError(request, response)
|
||||
);
|
||||
} else {
|
||||
fulfill(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
close(callback) {
|
||||
const closeWebSocket = (callback) => {
|
||||
// don't close if it's already closed
|
||||
if (this._ws.readyState === 3) {
|
||||
callback();
|
||||
} else {
|
||||
// don't notify on user-initiated shutdown ('disconnect' event)
|
||||
this._ws.removeAllListeners('close');
|
||||
this._ws.once('close', () => {
|
||||
this._ws.removeAllListeners();
|
||||
this._handleConnectionClose();
|
||||
callback();
|
||||
});
|
||||
this._ws.close();
|
||||
}
|
||||
};
|
||||
if (typeof callback === 'function') {
|
||||
closeWebSocket(callback);
|
||||
return undefined;
|
||||
} else {
|
||||
return new Promise((fulfill, reject) => {
|
||||
closeWebSocket(fulfill);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// initiate the connection process
|
||||
async _start() {
|
||||
const options = {
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
secure: this.secure,
|
||||
useHostName: this.useHostName,
|
||||
alterPath: this.alterPath
|
||||
};
|
||||
try {
|
||||
// fetch the WebSocket debugger URL
|
||||
const url = await this._fetchDebuggerURL(options);
|
||||
// allow the user to alter the URL
|
||||
const urlObject = parseUrl(url);
|
||||
urlObject.pathname = options.alterPath(urlObject.pathname);
|
||||
this.webSocketUrl = formatUrl(urlObject);
|
||||
// update the connection parameters using the debugging URL
|
||||
options.host = urlObject.hostname;
|
||||
options.port = urlObject.port || options.port;
|
||||
// fetch the protocol and prepare the API
|
||||
const protocol = await this._fetchProtocol(options);
|
||||
api.prepare(this, protocol);
|
||||
// finally connect to the WebSocket
|
||||
await this._connectToWebSocket();
|
||||
// since the handler is executed synchronously, the emit() must be
|
||||
// performed in the next tick so that uncaught errors in the client code
|
||||
// are not intercepted by the Promise mechanism and therefore reported
|
||||
// via the 'error' event
|
||||
process.nextTick(() => {
|
||||
this._notifier.emit('connect', this);
|
||||
});
|
||||
} catch (err) {
|
||||
this._notifier.emit('error', err);
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the WebSocket URL according to 'target'
|
||||
async _fetchDebuggerURL(options) {
|
||||
const userTarget = this.target;
|
||||
switch (typeof userTarget) {
|
||||
case 'string': {
|
||||
let idOrUrl = userTarget;
|
||||
// use default host and port if omitted (and a relative URL is specified)
|
||||
if (idOrUrl.startsWith('/')) {
|
||||
idOrUrl = `ws://${this.host}:${this.port}${idOrUrl}`;
|
||||
}
|
||||
// a WebSocket URL is specified by the user (e.g., node-inspector)
|
||||
if (idOrUrl.match(/^wss?:/i)) {
|
||||
return idOrUrl; // done!
|
||||
}
|
||||
// a target id is specified by the user
|
||||
else {
|
||||
const targets = await devtools.List(options);
|
||||
const object = targets.find((target) => target.id === idOrUrl);
|
||||
return object.webSocketDebuggerUrl;
|
||||
}
|
||||
}
|
||||
case 'object': {
|
||||
const object = userTarget;
|
||||
return object.webSocketDebuggerUrl;
|
||||
}
|
||||
case 'function': {
|
||||
const func = userTarget;
|
||||
const targets = await devtools.List(options);
|
||||
const result = func(targets);
|
||||
const object = typeof result === 'number' ? targets[result] : result;
|
||||
return object.webSocketDebuggerUrl;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid target argument "${this.target}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the protocol according to 'protocol' and 'local'
|
||||
async _fetchProtocol(options) {
|
||||
// if a protocol has been provided then use it
|
||||
if (this.protocol) {
|
||||
return this.protocol;
|
||||
}
|
||||
// otherwise user either the local or the remote version
|
||||
else {
|
||||
options.local = this.local;
|
||||
return await devtools.Protocol(options);
|
||||
}
|
||||
}
|
||||
|
||||
// establish the WebSocket connection and start processing user commands
|
||||
_connectToWebSocket() {
|
||||
return new Promise((fulfill, reject) => {
|
||||
// create the WebSocket
|
||||
try {
|
||||
if (this.secure) {
|
||||
this.webSocketUrl = this.webSocketUrl.replace(/^ws:/i, 'wss:');
|
||||
}
|
||||
this._ws = new WebSocket(this.webSocketUrl, [], {
|
||||
maxPayload: 256 * 1024 * 1024,
|
||||
perMessageDeflate: false,
|
||||
followRedirects: true,
|
||||
});
|
||||
} catch (err) {
|
||||
// handles bad URLs
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
// set up event handlers
|
||||
this._ws.on('open', () => {
|
||||
fulfill();
|
||||
});
|
||||
this._ws.on('message', (data) => {
|
||||
const message = JSON.parse(data);
|
||||
this._handleMessage(message);
|
||||
});
|
||||
this._ws.on('close', (code) => {
|
||||
this._handleConnectionClose();
|
||||
this.emit('disconnect');
|
||||
});
|
||||
this._ws.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_handleConnectionClose() {
|
||||
// make sure to complete all the unresolved callbacks
|
||||
const err = new Error('WebSocket connection closed');
|
||||
for (const callback of Object.values(this._callbacks)) {
|
||||
callback(err);
|
||||
}
|
||||
this._callbacks = {};
|
||||
}
|
||||
|
||||
// handle the messages read from the WebSocket
|
||||
_handleMessage(message) {
|
||||
// command response
|
||||
if (message.id) {
|
||||
const callback = this._callbacks[message.id];
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
// interpret the lack of both 'error' and 'result' as success
|
||||
// (this may happen with node-inspector)
|
||||
if (message.error) {
|
||||
callback(true, message.error);
|
||||
} else {
|
||||
callback(false, message.result || {});
|
||||
}
|
||||
// unregister command response callback
|
||||
delete this._callbacks[message.id];
|
||||
// notify when there are no more pending commands
|
||||
if (Object.keys(this._callbacks).length === 0) {
|
||||
this.emit('ready');
|
||||
}
|
||||
}
|
||||
// event
|
||||
else if (message.method) {
|
||||
const {method, params, sessionId} = message;
|
||||
this.emit('event', message);
|
||||
this.emit(method, params, sessionId);
|
||||
this.emit(`${method}.${sessionId}`, params, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
// send a command to the remote endpoint and register a callback for the reply
|
||||
_enqueueCommand(method, params, sessionId, callback) {
|
||||
const id = this._nextCommandId++;
|
||||
const message = {
|
||||
id,
|
||||
method,
|
||||
sessionId,
|
||||
params: params || {}
|
||||
};
|
||||
this._ws.send(JSON.stringify(message), (err) => {
|
||||
if (err) {
|
||||
// handle low-level WebSocket errors
|
||||
if (typeof callback === 'function') {
|
||||
callback(err);
|
||||
}
|
||||
} else {
|
||||
this._callbacks[id] = callback;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Chrome;
|
||||
4
node_modules/chrome-remote-interface/lib/defaults.js
generated
vendored
Normal file
4
node_modules/chrome-remote-interface/lib/defaults.js
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
module.exports.HOST = 'localhost';
|
||||
module.exports.PORT = 9222;
|
||||
127
node_modules/chrome-remote-interface/lib/devtools.js
generated
vendored
Normal file
127
node_modules/chrome-remote-interface/lib/devtools.js
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
const defaults = require('./defaults.js');
|
||||
const externalRequest = require('./external-request.js');
|
||||
|
||||
// options.path must be specified; callback(err, data)
|
||||
function devToolsInterface(path, options, callback) {
|
||||
const transport = options.secure ? https : http;
|
||||
const requestOptions = {
|
||||
method: options.method,
|
||||
host: options.host || defaults.HOST,
|
||||
port: options.port || defaults.PORT,
|
||||
useHostName: options.useHostName,
|
||||
path: (options.alterPath ? options.alterPath(path) : path)
|
||||
};
|
||||
externalRequest(transport, requestOptions, callback);
|
||||
}
|
||||
|
||||
// wrapper that allows to return a promise if the callback is omitted, it works
|
||||
// for DevTools methods
|
||||
function promisesWrapper(func) {
|
||||
return (options, callback) => {
|
||||
// options is an optional argument
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = undefined;
|
||||
}
|
||||
options = options || {};
|
||||
// just call the function otherwise wrap a promise around its execution
|
||||
if (typeof callback === 'function') {
|
||||
func(options, callback);
|
||||
return undefined;
|
||||
} else {
|
||||
return new Promise((fulfill, reject) => {
|
||||
func(options, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
fulfill(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function Protocol(options, callback) {
|
||||
// if the local protocol is requested
|
||||
if (options.local) {
|
||||
const localDescriptor = require('./protocol.json');
|
||||
callback(null, localDescriptor);
|
||||
return;
|
||||
}
|
||||
// try to fetch the protocol remotely
|
||||
devToolsInterface('/json/protocol', options, (err, descriptor) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, JSON.parse(descriptor));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function List(options, callback) {
|
||||
devToolsInterface('/json/list', options, (err, tabs) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, JSON.parse(tabs));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function New(options, callback) {
|
||||
let path = '/json/new';
|
||||
if (Object.prototype.hasOwnProperty.call(options, 'url')) {
|
||||
path += `?${options.url}`;
|
||||
}
|
||||
options.method = options.method || 'PUT'; // see #497
|
||||
devToolsInterface(path, options, (err, tab) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, JSON.parse(tab));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function Activate(options, callback) {
|
||||
devToolsInterface('/json/activate/' + options.id, options, (err) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function Close(options, callback) {
|
||||
devToolsInterface('/json/close/' + options.id, options, (err) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function Version(options, callback) {
|
||||
devToolsInterface('/json/version', options, (err, versionInfo) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, JSON.parse(versionInfo));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.Protocol = promisesWrapper(Protocol);
|
||||
module.exports.List = promisesWrapper(List);
|
||||
module.exports.New = promisesWrapper(New);
|
||||
module.exports.Activate = promisesWrapper(Activate);
|
||||
module.exports.Close = promisesWrapper(Close);
|
||||
module.exports.Version = promisesWrapper(Version);
|
||||
16
node_modules/chrome-remote-interface/lib/errors.js
generated
vendored
Normal file
16
node_modules/chrome-remote-interface/lib/errors.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
class ProtocolError extends Error {
|
||||
constructor(request, response) {
|
||||
let {message} = response;
|
||||
if (response.data) {
|
||||
message += ` (${response.data})`;
|
||||
}
|
||||
super(message);
|
||||
// attach the original response as well
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.ProtocolError = ProtocolError;
|
||||
44
node_modules/chrome-remote-interface/lib/external-request.js
generated
vendored
Normal file
44
node_modules/chrome-remote-interface/lib/external-request.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
const dns = require('dns');
|
||||
const util = require('util');
|
||||
|
||||
const REQUEST_TIMEOUT = 10000;
|
||||
|
||||
// callback(err, data)
|
||||
async function externalRequest(transport, options, callback) {
|
||||
// perform the DNS lookup manually so that the HTTP host header generated by
|
||||
// http.get will contain the IP address, this is needed because since Chrome
|
||||
// 66 the host header cannot contain an host name different than localhost
|
||||
// (see https://github.com/cyrus-and/chrome-remote-interface/issues/340)
|
||||
if (!options.useHostName) {
|
||||
try {
|
||||
const {address} = await util.promisify(dns.lookup)(options.host);
|
||||
options.host = address;
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// perform the actual request
|
||||
const request = transport.request(options, (response) => {
|
||||
let data = '';
|
||||
response.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
response.on('end', () => {
|
||||
if (response.statusCode === 200) {
|
||||
callback(null, data);
|
||||
} else {
|
||||
callback(new Error(data));
|
||||
}
|
||||
});
|
||||
});
|
||||
request.setTimeout(REQUEST_TIMEOUT, () => {
|
||||
request.abort();
|
||||
});
|
||||
request.on('error', callback);
|
||||
request.end();
|
||||
}
|
||||
|
||||
module.exports = externalRequest;
|
||||
27862
node_modules/chrome-remote-interface/lib/protocol.json
generated
vendored
Normal file
27862
node_modules/chrome-remote-interface/lib/protocol.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
39
node_modules/chrome-remote-interface/lib/websocket-wrapper.js
generated
vendored
Normal file
39
node_modules/chrome-remote-interface/lib/websocket-wrapper.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
|
||||
// wrapper around the Node.js ws module
|
||||
// for use in browsers
|
||||
class WebSocketWrapper extends EventEmitter {
|
||||
constructor(url) {
|
||||
super();
|
||||
this._ws = new WebSocket(url); // eslint-disable-line no-undef
|
||||
this._ws.onopen = () => {
|
||||
this.emit('open');
|
||||
};
|
||||
this._ws.onclose = () => {
|
||||
this.emit('close');
|
||||
};
|
||||
this._ws.onmessage = (event) => {
|
||||
this.emit('message', event.data);
|
||||
};
|
||||
this._ws.onerror = () => {
|
||||
this.emit('error', new Error('WebSocket error'));
|
||||
};
|
||||
}
|
||||
|
||||
close() {
|
||||
this._ws.close();
|
||||
}
|
||||
|
||||
send(data, callback) {
|
||||
try {
|
||||
this._ws.send(data);
|
||||
callback();
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketWrapper;
|
||||
21
node_modules/chrome-remote-interface/node_modules/ws/LICENSE
generated
vendored
Normal file
21
node_modules/chrome-remote-interface/node_modules/ws/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
495
node_modules/chrome-remote-interface/node_modules/ws/README.md
generated
vendored
Normal file
495
node_modules/chrome-remote-interface/node_modules/ws/README.md
generated
vendored
Normal file
@@ -0,0 +1,495 @@
|
||||
# ws: a Node.js WebSocket library
|
||||
|
||||
[](https://www.npmjs.com/package/ws)
|
||||
[](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)
|
||||
[](https://coveralls.io/github/websockets/ws)
|
||||
|
||||
ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and
|
||||
server implementation.
|
||||
|
||||
Passes the quite extensive Autobahn test suite: [server][server-report],
|
||||
[client][client-report].
|
||||
|
||||
**Note**: This module does not work in the browser. The client in the docs is a
|
||||
reference to a back end with the role of a client in the WebSocket
|
||||
communication. Browser clients must use the native
|
||||
[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
||||
object. To make the same code work seamlessly on Node.js and the browser, you
|
||||
can use one of the many wrappers available on npm, like
|
||||
[isomorphic-ws](https://github.com/heineiuo/isomorphic-ws).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Protocol support](#protocol-support)
|
||||
- [Installing](#installing)
|
||||
- [Opt-in for performance](#opt-in-for-performance)
|
||||
- [API docs](#api-docs)
|
||||
- [WebSocket compression](#websocket-compression)
|
||||
- [Usage examples](#usage-examples)
|
||||
- [Sending and receiving text data](#sending-and-receiving-text-data)
|
||||
- [Sending binary data](#sending-binary-data)
|
||||
- [Simple server](#simple-server)
|
||||
- [External HTTP/S server](#external-https-server)
|
||||
- [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server)
|
||||
- [Client authentication](#client-authentication)
|
||||
- [Server broadcast](#server-broadcast)
|
||||
- [echo.websocket.org demo](#echowebsocketorg-demo)
|
||||
- [Use the Node.js streams API](#use-the-nodejs-streams-api)
|
||||
- [Other examples](#other-examples)
|
||||
- [FAQ](#faq)
|
||||
- [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)
|
||||
- [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)
|
||||
- [How to connect via a proxy?](#how-to-connect-via-a-proxy)
|
||||
- [Changelog](#changelog)
|
||||
- [License](#license)
|
||||
|
||||
## Protocol support
|
||||
|
||||
- **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
|
||||
- **HyBi drafts 13-17** (Current default, alternatively option
|
||||
`protocolVersion: 13`)
|
||||
|
||||
## Installing
|
||||
|
||||
```
|
||||
npm install ws
|
||||
```
|
||||
|
||||
### Opt-in for performance
|
||||
|
||||
There are 2 optional modules that can be installed along side with the ws
|
||||
module. These modules are binary addons which improve certain operations.
|
||||
Prebuilt binaries are available for the most popular platforms so you don't
|
||||
necessarily need to have a C++ compiler installed on your machine.
|
||||
|
||||
- `npm install --save-optional bufferutil`: Allows to efficiently perform
|
||||
operations such as masking and unmasking the data payload of the WebSocket
|
||||
frames.
|
||||
- `npm install --save-optional utf-8-validate`: Allows to efficiently check if a
|
||||
message contains valid UTF-8.
|
||||
|
||||
## API docs
|
||||
|
||||
See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and
|
||||
utility functions.
|
||||
|
||||
## WebSocket compression
|
||||
|
||||
ws supports the [permessage-deflate extension][permessage-deflate] which enables
|
||||
the client and server to negotiate a compression algorithm and its parameters,
|
||||
and then selectively apply it to the data payloads of each WebSocket message.
|
||||
|
||||
The extension is disabled by default on the server and enabled by default on the
|
||||
client. It adds a significant overhead in terms of performance and memory
|
||||
consumption so we suggest to enable it only if it is really needed.
|
||||
|
||||
Note that Node.js has a variety of issues with high-performance compression,
|
||||
where increased concurrency, especially on Linux, can lead to [catastrophic
|
||||
memory fragmentation][node-zlib-bug] and slow performance. If you intend to use
|
||||
permessage-deflate in production, it is worthwhile to set up a test
|
||||
representative of your workload and ensure Node.js/zlib will handle it with
|
||||
acceptable performance and memory usage.
|
||||
|
||||
Tuning of permessage-deflate can be done via the options defined below. You can
|
||||
also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly
|
||||
into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs].
|
||||
|
||||
See [the docs][ws-server-options] for more options.
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({
|
||||
port: 8080,
|
||||
perMessageDeflate: {
|
||||
zlibDeflateOptions: {
|
||||
// See zlib defaults.
|
||||
chunkSize: 1024,
|
||||
memLevel: 7,
|
||||
level: 3
|
||||
},
|
||||
zlibInflateOptions: {
|
||||
chunkSize: 10 * 1024
|
||||
},
|
||||
// Other options settable:
|
||||
clientNoContextTakeover: true, // Defaults to negotiated value.
|
||||
serverNoContextTakeover: true, // Defaults to negotiated value.
|
||||
serverMaxWindowBits: 10, // Defaults to negotiated value.
|
||||
// Below options specified as default values.
|
||||
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
||||
threshold: 1024 // Size (in bytes) below which messages
|
||||
// should not be compressed.
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The client will only use the extension if it is supported and enabled on the
|
||||
server. To always disable the extension on the client set the
|
||||
`perMessageDeflate` option to `false`.
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://www.host.com/path', {
|
||||
perMessageDeflate: false
|
||||
});
|
||||
```
|
||||
|
||||
## Usage examples
|
||||
|
||||
### Sending and receiving text data
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://www.host.com/path');
|
||||
|
||||
ws.on('open', function open() {
|
||||
ws.send('something');
|
||||
});
|
||||
|
||||
ws.on('message', function incoming(data) {
|
||||
console.log(data);
|
||||
});
|
||||
```
|
||||
|
||||
### Sending binary data
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://www.host.com/path');
|
||||
|
||||
ws.on('open', function open() {
|
||||
const array = new Float32Array(5);
|
||||
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
array[i] = i / 2;
|
||||
}
|
||||
|
||||
ws.send(array);
|
||||
});
|
||||
```
|
||||
|
||||
### Simple server
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.on('message', function incoming(message) {
|
||||
console.log('received: %s', message);
|
||||
});
|
||||
|
||||
ws.send('something');
|
||||
});
|
||||
```
|
||||
|
||||
### External HTTP/S server
|
||||
|
||||
```js
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const server = https.createServer({
|
||||
cert: fs.readFileSync('/path/to/cert.pem'),
|
||||
key: fs.readFileSync('/path/to/key.pem')
|
||||
});
|
||||
const wss = new WebSocket.Server({ server });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.on('message', function incoming(message) {
|
||||
console.log('received: %s', message);
|
||||
});
|
||||
|
||||
ws.send('something');
|
||||
});
|
||||
|
||||
server.listen(8080);
|
||||
```
|
||||
|
||||
### Multiple servers sharing a single HTTP/S server
|
||||
|
||||
```js
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const url = require('url');
|
||||
|
||||
const server = http.createServer();
|
||||
const wss1 = new WebSocket.Server({ noServer: true });
|
||||
const wss2 = new WebSocket.Server({ noServer: true });
|
||||
|
||||
wss1.on('connection', function connection(ws) {
|
||||
// ...
|
||||
});
|
||||
|
||||
wss2.on('connection', function connection(ws) {
|
||||
// ...
|
||||
});
|
||||
|
||||
server.on('upgrade', function upgrade(request, socket, head) {
|
||||
const pathname = url.parse(request.url).pathname;
|
||||
|
||||
if (pathname === '/foo') {
|
||||
wss1.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss1.emit('connection', ws, request);
|
||||
});
|
||||
} else if (pathname === '/bar') {
|
||||
wss2.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss2.emit('connection', ws, request);
|
||||
});
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(8080);
|
||||
```
|
||||
|
||||
### Client authentication
|
||||
|
||||
```js
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const server = http.createServer();
|
||||
const wss = new WebSocket.Server({ noServer: true });
|
||||
|
||||
wss.on('connection', function connection(ws, request, client) {
|
||||
ws.on('message', function message(msg) {
|
||||
console.log(`Received message ${msg} from user ${client}`);
|
||||
});
|
||||
});
|
||||
|
||||
server.on('upgrade', function upgrade(request, socket, head) {
|
||||
// This function is not defined on purpose. Implement it with your own logic.
|
||||
authenticate(request, (err, client) => {
|
||||
if (err || !client) {
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss.emit('connection', ws, request, client);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(8080);
|
||||
```
|
||||
|
||||
Also see the provided [example][session-parse-example] using `express-session`.
|
||||
|
||||
### Server broadcast
|
||||
|
||||
A client WebSocket broadcasting to all connected WebSocket clients, including
|
||||
itself.
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.on('message', function incoming(data) {
|
||||
wss.clients.forEach(function each(client) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
A client WebSocket broadcasting to every other connected WebSocket clients,
|
||||
excluding itself.
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.on('message', function incoming(data) {
|
||||
wss.clients.forEach(function each(client) {
|
||||
if (client !== ws && client.readyState === WebSocket.OPEN) {
|
||||
client.send(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### echo.websocket.org demo
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('wss://echo.websocket.org/', {
|
||||
origin: 'https://websocket.org'
|
||||
});
|
||||
|
||||
ws.on('open', function open() {
|
||||
console.log('connected');
|
||||
ws.send(Date.now());
|
||||
});
|
||||
|
||||
ws.on('close', function close() {
|
||||
console.log('disconnected');
|
||||
});
|
||||
|
||||
ws.on('message', function incoming(data) {
|
||||
console.log(`Roundtrip time: ${Date.now() - data} ms`);
|
||||
|
||||
setTimeout(function timeout() {
|
||||
ws.send(Date.now());
|
||||
}, 500);
|
||||
});
|
||||
```
|
||||
|
||||
### Use the Node.js streams API
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('wss://echo.websocket.org/', {
|
||||
origin: 'https://websocket.org'
|
||||
});
|
||||
|
||||
const duplex = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' });
|
||||
|
||||
duplex.pipe(process.stdout);
|
||||
process.stdin.pipe(duplex);
|
||||
```
|
||||
|
||||
### Other examples
|
||||
|
||||
For a full example with a browser client communicating with a ws server, see the
|
||||
examples folder.
|
||||
|
||||
Otherwise, see the test cases.
|
||||
|
||||
## FAQ
|
||||
|
||||
### How to get the IP address of the client?
|
||||
|
||||
The remote IP address can be obtained from the raw socket.
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
wss.on('connection', function connection(ws, req) {
|
||||
const ip = req.socket.remoteAddress;
|
||||
});
|
||||
```
|
||||
|
||||
When the server runs behind a proxy like NGINX, the de-facto standard is to use
|
||||
the `X-Forwarded-For` header.
|
||||
|
||||
```js
|
||||
wss.on('connection', function connection(ws, req) {
|
||||
const ip = req.headers['x-forwarded-for'].split(',')[0].trim();
|
||||
});
|
||||
```
|
||||
|
||||
### How to detect and close broken connections?
|
||||
|
||||
Sometimes the link between the server and the client can be interrupted in a way
|
||||
that keeps both the server and the client unaware of the broken state of the
|
||||
connection (e.g. when pulling the cord).
|
||||
|
||||
In these cases ping messages can be used as a means to verify that the remote
|
||||
endpoint is still responsive.
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
function noop() {}
|
||||
|
||||
function heartbeat() {
|
||||
this.isAlive = true;
|
||||
}
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.isAlive = true;
|
||||
ws.on('pong', heartbeat);
|
||||
});
|
||||
|
||||
const interval = setInterval(function ping() {
|
||||
wss.clients.forEach(function each(ws) {
|
||||
if (ws.isAlive === false) return ws.terminate();
|
||||
|
||||
ws.isAlive = false;
|
||||
ws.ping(noop);
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
wss.on('close', function close() {
|
||||
clearInterval(interval);
|
||||
});
|
||||
```
|
||||
|
||||
Pong messages are automatically sent in response to ping messages as required by
|
||||
the spec.
|
||||
|
||||
Just like the server example above your clients might as well lose connection
|
||||
without knowing it. You might want to add a ping listener on your clients to
|
||||
prevent that. A simple implementation would be:
|
||||
|
||||
```js
|
||||
const WebSocket = require('ws');
|
||||
|
||||
function heartbeat() {
|
||||
clearTimeout(this.pingTimeout);
|
||||
|
||||
// Use `WebSocket#terminate()`, which immediately destroys the connection,
|
||||
// instead of `WebSocket#close()`, which waits for the close timer.
|
||||
// Delay should be equal to the interval at which your server
|
||||
// sends out pings plus a conservative assumption of the latency.
|
||||
this.pingTimeout = setTimeout(() => {
|
||||
this.terminate();
|
||||
}, 30000 + 1000);
|
||||
}
|
||||
|
||||
const client = new WebSocket('wss://echo.websocket.org/');
|
||||
|
||||
client.on('open', heartbeat);
|
||||
client.on('ping', heartbeat);
|
||||
client.on('close', function clear() {
|
||||
clearTimeout(this.pingTimeout);
|
||||
});
|
||||
```
|
||||
|
||||
### How to connect via a proxy?
|
||||
|
||||
Use a custom `http.Agent` implementation like [https-proxy-agent][] or
|
||||
[socks-proxy-agent][].
|
||||
|
||||
## Changelog
|
||||
|
||||
We're using the GitHub [releases][changelog] for changelog entries.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[changelog]: https://github.com/websockets/ws/releases
|
||||
[client-report]: http://websockets.github.io/ws/autobahn/clients/
|
||||
[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent
|
||||
[node-zlib-bug]: https://github.com/nodejs/node/issues/8871
|
||||
[node-zlib-deflaterawdocs]:
|
||||
https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options
|
||||
[permessage-deflate]: https://tools.ietf.org/html/rfc7692
|
||||
[server-report]: http://websockets.github.io/ws/autobahn/servers/
|
||||
[session-parse-example]: ./examples/express-session-parse
|
||||
[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
|
||||
[ws-server-options]:
|
||||
https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback
|
||||
8
node_modules/chrome-remote-interface/node_modules/ws/browser.js
generated
vendored
Normal file
8
node_modules/chrome-remote-interface/node_modules/ws/browser.js
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function () {
|
||||
throw new Error(
|
||||
'ws does not work in the browser. Browser clients must use the native ' +
|
||||
'WebSocket object'
|
||||
);
|
||||
};
|
||||
10
node_modules/chrome-remote-interface/node_modules/ws/index.js
generated
vendored
Normal file
10
node_modules/chrome-remote-interface/node_modules/ws/index.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const WebSocket = require('./lib/websocket');
|
||||
|
||||
WebSocket.createWebSocketStream = require('./lib/stream');
|
||||
WebSocket.Server = require('./lib/websocket-server');
|
||||
WebSocket.Receiver = require('./lib/receiver');
|
||||
WebSocket.Sender = require('./lib/sender');
|
||||
|
||||
module.exports = WebSocket;
|
||||
129
node_modules/chrome-remote-interface/node_modules/ws/lib/buffer-util.js
generated
vendored
Normal file
129
node_modules/chrome-remote-interface/node_modules/ws/lib/buffer-util.js
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
'use strict';
|
||||
|
||||
const { EMPTY_BUFFER } = require('./constants');
|
||||
|
||||
/**
|
||||
* Merges an array of buffers into a new buffer.
|
||||
*
|
||||
* @param {Buffer[]} list The array of buffers to concat
|
||||
* @param {Number} totalLength The total length of buffers in the list
|
||||
* @return {Buffer} The resulting buffer
|
||||
* @public
|
||||
*/
|
||||
function concat(list, totalLength) {
|
||||
if (list.length === 0) return EMPTY_BUFFER;
|
||||
if (list.length === 1) return list[0];
|
||||
|
||||
const target = Buffer.allocUnsafe(totalLength);
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const buf = list[i];
|
||||
target.set(buf, offset);
|
||||
offset += buf.length;
|
||||
}
|
||||
|
||||
if (offset < totalLength) return target.slice(0, offset);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Masks a buffer using the given mask.
|
||||
*
|
||||
* @param {Buffer} source The buffer to mask
|
||||
* @param {Buffer} mask The mask to use
|
||||
* @param {Buffer} output The buffer where to store the result
|
||||
* @param {Number} offset The offset at which to start writing
|
||||
* @param {Number} length The number of bytes to mask.
|
||||
* @public
|
||||
*/
|
||||
function _mask(source, mask, output, offset, length) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
output[offset + i] = source[i] ^ mask[i & 3];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmasks a buffer using the given mask.
|
||||
*
|
||||
* @param {Buffer} buffer The buffer to unmask
|
||||
* @param {Buffer} mask The mask to use
|
||||
* @public
|
||||
*/
|
||||
function _unmask(buffer, mask) {
|
||||
// Required until https://github.com/nodejs/node/issues/9006 is resolved.
|
||||
const length = buffer.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
buffer[i] ^= mask[i & 3];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a buffer to an `ArrayBuffer`.
|
||||
*
|
||||
* @param {Buffer} buf The buffer to convert
|
||||
* @return {ArrayBuffer} Converted buffer
|
||||
* @public
|
||||
*/
|
||||
function toArrayBuffer(buf) {
|
||||
if (buf.byteLength === buf.buffer.byteLength) {
|
||||
return buf.buffer;
|
||||
}
|
||||
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `data` to a `Buffer`.
|
||||
*
|
||||
* @param {*} data The data to convert
|
||||
* @return {Buffer} The buffer
|
||||
* @throws {TypeError}
|
||||
* @public
|
||||
*/
|
||||
function toBuffer(data) {
|
||||
toBuffer.readOnly = true;
|
||||
|
||||
if (Buffer.isBuffer(data)) return data;
|
||||
|
||||
let buf;
|
||||
|
||||
if (data instanceof ArrayBuffer) {
|
||||
buf = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||
} else {
|
||||
buf = Buffer.from(data);
|
||||
toBuffer.readOnly = false;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
try {
|
||||
const bufferUtil = require('bufferutil');
|
||||
const bu = bufferUtil.BufferUtil || bufferUtil;
|
||||
|
||||
module.exports = {
|
||||
concat,
|
||||
mask(source, mask, output, offset, length) {
|
||||
if (length < 48) _mask(source, mask, output, offset, length);
|
||||
else bu.mask(source, mask, output, offset, length);
|
||||
},
|
||||
toArrayBuffer,
|
||||
toBuffer,
|
||||
unmask(buffer, mask) {
|
||||
if (buffer.length < 32) _unmask(buffer, mask);
|
||||
else bu.unmask(buffer, mask);
|
||||
}
|
||||
};
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
module.exports = {
|
||||
concat,
|
||||
mask: _mask,
|
||||
toArrayBuffer,
|
||||
toBuffer,
|
||||
unmask: _unmask
|
||||
};
|
||||
}
|
||||
10
node_modules/chrome-remote-interface/node_modules/ws/lib/constants.js
generated
vendored
Normal file
10
node_modules/chrome-remote-interface/node_modules/ws/lib/constants.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
|
||||
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
|
||||
kStatusCode: Symbol('status-code'),
|
||||
kWebSocket: Symbol('websocket'),
|
||||
EMPTY_BUFFER: Buffer.alloc(0),
|
||||
NOOP: () => {}
|
||||
};
|
||||
184
node_modules/chrome-remote-interface/node_modules/ws/lib/event-target.js
generated
vendored
Normal file
184
node_modules/chrome-remote-interface/node_modules/ws/lib/event-target.js
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class representing an event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class Event {
|
||||
/**
|
||||
* Create a new `Event`.
|
||||
*
|
||||
* @param {String} type The name of the event
|
||||
* @param {Object} target A reference to the target to which the event was
|
||||
* dispatched
|
||||
*/
|
||||
constructor(type, target) {
|
||||
this.target = target;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a message event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class MessageEvent extends Event {
|
||||
/**
|
||||
* Create a new `MessageEvent`.
|
||||
*
|
||||
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
|
||||
* @param {WebSocket} target A reference to the target to which the event was
|
||||
* dispatched
|
||||
*/
|
||||
constructor(data, target) {
|
||||
super('message', target);
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a close event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class CloseEvent extends Event {
|
||||
/**
|
||||
* Create a new `CloseEvent`.
|
||||
*
|
||||
* @param {Number} code The status code explaining why the connection is being
|
||||
* closed
|
||||
* @param {String} reason A human-readable string explaining why the
|
||||
* connection is closing
|
||||
* @param {WebSocket} target A reference to the target to which the event was
|
||||
* dispatched
|
||||
*/
|
||||
constructor(code, reason, target) {
|
||||
super('close', target);
|
||||
|
||||
this.wasClean = target._closeFrameReceived && target._closeFrameSent;
|
||||
this.reason = reason;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an open event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class OpenEvent extends Event {
|
||||
/**
|
||||
* Create a new `OpenEvent`.
|
||||
*
|
||||
* @param {WebSocket} target A reference to the target to which the event was
|
||||
* dispatched
|
||||
*/
|
||||
constructor(target) {
|
||||
super('open', target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an error event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class ErrorEvent extends Event {
|
||||
/**
|
||||
* Create a new `ErrorEvent`.
|
||||
*
|
||||
* @param {Object} error The error that generated this event
|
||||
* @param {WebSocket} target A reference to the target to which the event was
|
||||
* dispatched
|
||||
*/
|
||||
constructor(error, target) {
|
||||
super('error', target);
|
||||
|
||||
this.message = error.message;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides methods for emulating the `EventTarget` interface. It's not
|
||||
* meant to be used directly.
|
||||
*
|
||||
* @mixin
|
||||
*/
|
||||
const EventTarget = {
|
||||
/**
|
||||
* Register an event listener.
|
||||
*
|
||||
* @param {String} type A string representing the event type to listen for
|
||||
* @param {Function} listener The listener to add
|
||||
* @param {Object} [options] An options object specifies characteristics about
|
||||
* the event listener
|
||||
* @param {Boolean} [options.once=false] A `Boolean`` indicating that the
|
||||
* listener should be invoked at most once after being added. If `true`,
|
||||
* the listener would be automatically removed when invoked.
|
||||
* @public
|
||||
*/
|
||||
addEventListener(type, listener, options) {
|
||||
if (typeof listener !== 'function') return;
|
||||
|
||||
function onMessage(data) {
|
||||
listener.call(this, new MessageEvent(data, this));
|
||||
}
|
||||
|
||||
function onClose(code, message) {
|
||||
listener.call(this, new CloseEvent(code, message, this));
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
listener.call(this, new ErrorEvent(error, this));
|
||||
}
|
||||
|
||||
function onOpen() {
|
||||
listener.call(this, new OpenEvent(this));
|
||||
}
|
||||
|
||||
const method = options && options.once ? 'once' : 'on';
|
||||
|
||||
if (type === 'message') {
|
||||
onMessage._listener = listener;
|
||||
this[method](type, onMessage);
|
||||
} else if (type === 'close') {
|
||||
onClose._listener = listener;
|
||||
this[method](type, onClose);
|
||||
} else if (type === 'error') {
|
||||
onError._listener = listener;
|
||||
this[method](type, onError);
|
||||
} else if (type === 'open') {
|
||||
onOpen._listener = listener;
|
||||
this[method](type, onOpen);
|
||||
} else {
|
||||
this[method](type, listener);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener.
|
||||
*
|
||||
* @param {String} type A string representing the event type to remove
|
||||
* @param {Function} listener The listener to remove
|
||||
* @public
|
||||
*/
|
||||
removeEventListener(type, listener) {
|
||||
const listeners = this.listeners(type);
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i] === listener || listeners[i]._listener === listener) {
|
||||
this.removeListener(type, listeners[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EventTarget;
|
||||
223
node_modules/chrome-remote-interface/node_modules/ws/lib/extension.js
generated
vendored
Normal file
223
node_modules/chrome-remote-interface/node_modules/ws/lib/extension.js
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
'use strict';
|
||||
|
||||
//
|
||||
// Allowed token characters:
|
||||
//
|
||||
// '!', '#', '$', '%', '&', ''', '*', '+', '-',
|
||||
// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
|
||||
//
|
||||
// tokenChars[32] === 0 // ' '
|
||||
// tokenChars[33] === 1 // '!'
|
||||
// tokenChars[34] === 0 // '"'
|
||||
// ...
|
||||
//
|
||||
// prettier-ignore
|
||||
const tokenChars = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
|
||||
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
|
||||
];
|
||||
|
||||
/**
|
||||
* Adds an offer to the map of extension offers or a parameter to the map of
|
||||
* parameters.
|
||||
*
|
||||
* @param {Object} dest The map of extension offers or parameters
|
||||
* @param {String} name The extension or parameter name
|
||||
* @param {(Object|Boolean|String)} elem The extension parameters or the
|
||||
* parameter value
|
||||
* @private
|
||||
*/
|
||||
function push(dest, name, elem) {
|
||||
if (dest[name] === undefined) dest[name] = [elem];
|
||||
else dest[name].push(elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the `Sec-WebSocket-Extensions` header into an object.
|
||||
*
|
||||
* @param {String} header The field value of the header
|
||||
* @return {Object} The parsed object
|
||||
* @public
|
||||
*/
|
||||
function parse(header) {
|
||||
const offers = Object.create(null);
|
||||
|
||||
if (header === undefined || header === '') return offers;
|
||||
|
||||
let params = Object.create(null);
|
||||
let mustUnescape = false;
|
||||
let isEscaping = false;
|
||||
let inQuotes = false;
|
||||
let extensionName;
|
||||
let paramName;
|
||||
let start = -1;
|
||||
let end = -1;
|
||||
let i = 0;
|
||||
|
||||
for (; i < header.length; i++) {
|
||||
const code = header.charCodeAt(i);
|
||||
|
||||
if (extensionName === undefined) {
|
||||
if (end === -1 && tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */) {
|
||||
if (end === -1 && start !== -1) end = i;
|
||||
} else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
|
||||
if (start === -1) {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
|
||||
if (end === -1) end = i;
|
||||
const name = header.slice(start, end);
|
||||
if (code === 0x2c) {
|
||||
push(offers, name, params);
|
||||
params = Object.create(null);
|
||||
} else {
|
||||
extensionName = name;
|
||||
}
|
||||
|
||||
start = end = -1;
|
||||
} else {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
} else if (paramName === undefined) {
|
||||
if (end === -1 && tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (code === 0x20 || code === 0x09) {
|
||||
if (end === -1 && start !== -1) end = i;
|
||||
} else if (code === 0x3b || code === 0x2c) {
|
||||
if (start === -1) {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
|
||||
if (end === -1) end = i;
|
||||
push(params, header.slice(start, end), true);
|
||||
if (code === 0x2c) {
|
||||
push(offers, extensionName, params);
|
||||
params = Object.create(null);
|
||||
extensionName = undefined;
|
||||
}
|
||||
|
||||
start = end = -1;
|
||||
} else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) {
|
||||
paramName = header.slice(start, i);
|
||||
start = end = -1;
|
||||
} else {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
} else {
|
||||
//
|
||||
// The value of a quoted-string after unescaping must conform to the
|
||||
// token ABNF, so only token characters are valid.
|
||||
// Ref: https://tools.ietf.org/html/rfc6455#section-9.1
|
||||
//
|
||||
if (isEscaping) {
|
||||
if (tokenChars[code] !== 1) {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
if (start === -1) start = i;
|
||||
else if (!mustUnescape) mustUnescape = true;
|
||||
isEscaping = false;
|
||||
} else if (inQuotes) {
|
||||
if (tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (code === 0x22 /* '"' */ && start !== -1) {
|
||||
inQuotes = false;
|
||||
end = i;
|
||||
} else if (code === 0x5c /* '\' */) {
|
||||
isEscaping = true;
|
||||
} else {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
} else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
|
||||
inQuotes = true;
|
||||
} else if (end === -1 && tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (start !== -1 && (code === 0x20 || code === 0x09)) {
|
||||
if (end === -1) end = i;
|
||||
} else if (code === 0x3b || code === 0x2c) {
|
||||
if (start === -1) {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
|
||||
if (end === -1) end = i;
|
||||
let value = header.slice(start, end);
|
||||
if (mustUnescape) {
|
||||
value = value.replace(/\\/g, '');
|
||||
mustUnescape = false;
|
||||
}
|
||||
push(params, paramName, value);
|
||||
if (code === 0x2c) {
|
||||
push(offers, extensionName, params);
|
||||
params = Object.create(null);
|
||||
extensionName = undefined;
|
||||
}
|
||||
|
||||
paramName = undefined;
|
||||
start = end = -1;
|
||||
} else {
|
||||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start === -1 || inQuotes) {
|
||||
throw new SyntaxError('Unexpected end of input');
|
||||
}
|
||||
|
||||
if (end === -1) end = i;
|
||||
const token = header.slice(start, end);
|
||||
if (extensionName === undefined) {
|
||||
push(offers, token, params);
|
||||
} else {
|
||||
if (paramName === undefined) {
|
||||
push(params, token, true);
|
||||
} else if (mustUnescape) {
|
||||
push(params, paramName, token.replace(/\\/g, ''));
|
||||
} else {
|
||||
push(params, paramName, token);
|
||||
}
|
||||
push(offers, extensionName, params);
|
||||
}
|
||||
|
||||
return offers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the `Sec-WebSocket-Extensions` header field value.
|
||||
*
|
||||
* @param {Object} extensions The map of extensions and parameters to format
|
||||
* @return {String} A string representing the given object
|
||||
* @public
|
||||
*/
|
||||
function format(extensions) {
|
||||
return Object.keys(extensions)
|
||||
.map((extension) => {
|
||||
let configurations = extensions[extension];
|
||||
if (!Array.isArray(configurations)) configurations = [configurations];
|
||||
return configurations
|
||||
.map((params) => {
|
||||
return [extension]
|
||||
.concat(
|
||||
Object.keys(params).map((k) => {
|
||||
let values = params[k];
|
||||
if (!Array.isArray(values)) values = [values];
|
||||
return values
|
||||
.map((v) => (v === true ? k : `${k}=${v}`))
|
||||
.join('; ');
|
||||
})
|
||||
)
|
||||
.join('; ');
|
||||
})
|
||||
.join(', ');
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
module.exports = { format, parse };
|
||||
55
node_modules/chrome-remote-interface/node_modules/ws/lib/limiter.js
generated
vendored
Normal file
55
node_modules/chrome-remote-interface/node_modules/ws/lib/limiter.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const kDone = Symbol('kDone');
|
||||
const kRun = Symbol('kRun');
|
||||
|
||||
/**
|
||||
* A very simple job queue with adjustable concurrency. Adapted from
|
||||
* https://github.com/STRML/async-limiter
|
||||
*/
|
||||
class Limiter {
|
||||
/**
|
||||
* Creates a new `Limiter`.
|
||||
*
|
||||
* @param {Number} [concurrency=Infinity] The maximum number of jobs allowed
|
||||
* to run concurrently
|
||||
*/
|
||||
constructor(concurrency) {
|
||||
this[kDone] = () => {
|
||||
this.pending--;
|
||||
this[kRun]();
|
||||
};
|
||||
this.concurrency = concurrency || Infinity;
|
||||
this.jobs = [];
|
||||
this.pending = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a job to the queue.
|
||||
*
|
||||
* @param {Function} job The job to run
|
||||
* @public
|
||||
*/
|
||||
add(job) {
|
||||
this.jobs.push(job);
|
||||
this[kRun]();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a job from the queue and runs it if possible.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
[kRun]() {
|
||||
if (this.pending === this.concurrency) return;
|
||||
|
||||
if (this.jobs.length) {
|
||||
const job = this.jobs.shift();
|
||||
|
||||
this.pending++;
|
||||
job(this[kDone]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Limiter;
|
||||
518
node_modules/chrome-remote-interface/node_modules/ws/lib/permessage-deflate.js
generated
vendored
Normal file
518
node_modules/chrome-remote-interface/node_modules/ws/lib/permessage-deflate.js
generated
vendored
Normal file
@@ -0,0 +1,518 @@
|
||||
'use strict';
|
||||
|
||||
const zlib = require('zlib');
|
||||
|
||||
const bufferUtil = require('./buffer-util');
|
||||
const Limiter = require('./limiter');
|
||||
const { kStatusCode, NOOP } = require('./constants');
|
||||
|
||||
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
||||
const kPerMessageDeflate = Symbol('permessage-deflate');
|
||||
const kTotalLength = Symbol('total-length');
|
||||
const kCallback = Symbol('callback');
|
||||
const kBuffers = Symbol('buffers');
|
||||
const kError = Symbol('error');
|
||||
|
||||
//
|
||||
// We limit zlib concurrency, which prevents severe memory fragmentation
|
||||
// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
|
||||
// and https://github.com/websockets/ws/issues/1202
|
||||
//
|
||||
// Intentionally global; it's the global thread pool that's an issue.
|
||||
//
|
||||
let zlibLimiter;
|
||||
|
||||
/**
|
||||
* permessage-deflate implementation.
|
||||
*/
|
||||
class PerMessageDeflate {
|
||||
/**
|
||||
* Creates a PerMessageDeflate instance.
|
||||
*
|
||||
* @param {Object} [options] Configuration options
|
||||
* @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
|
||||
* disabling of server context takeover
|
||||
* @param {Boolean} [options.clientNoContextTakeover=false] Advertise/
|
||||
* acknowledge disabling of client context takeover
|
||||
* @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
|
||||
* use of a custom server window size
|
||||
* @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support
|
||||
* for, or request, a custom client window size
|
||||
* @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on
|
||||
* deflate
|
||||
* @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
|
||||
* inflate
|
||||
* @param {Number} [options.threshold=1024] Size (in bytes) below which
|
||||
* messages should not be compressed
|
||||
* @param {Number} [options.concurrencyLimit=10] The number of concurrent
|
||||
* calls to zlib
|
||||
* @param {Boolean} [isServer=false] Create the instance in either server or
|
||||
* client mode
|
||||
* @param {Number} [maxPayload=0] The maximum allowed message length
|
||||
*/
|
||||
constructor(options, isServer, maxPayload) {
|
||||
this._maxPayload = maxPayload | 0;
|
||||
this._options = options || {};
|
||||
this._threshold =
|
||||
this._options.threshold !== undefined ? this._options.threshold : 1024;
|
||||
this._isServer = !!isServer;
|
||||
this._deflate = null;
|
||||
this._inflate = null;
|
||||
|
||||
this.params = null;
|
||||
|
||||
if (!zlibLimiter) {
|
||||
const concurrency =
|
||||
this._options.concurrencyLimit !== undefined
|
||||
? this._options.concurrencyLimit
|
||||
: 10;
|
||||
zlibLimiter = new Limiter(concurrency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
static get extensionName() {
|
||||
return 'permessage-deflate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an extension negotiation offer.
|
||||
*
|
||||
* @return {Object} Extension parameters
|
||||
* @public
|
||||
*/
|
||||
offer() {
|
||||
const params = {};
|
||||
|
||||
if (this._options.serverNoContextTakeover) {
|
||||
params.server_no_context_takeover = true;
|
||||
}
|
||||
if (this._options.clientNoContextTakeover) {
|
||||
params.client_no_context_takeover = true;
|
||||
}
|
||||
if (this._options.serverMaxWindowBits) {
|
||||
params.server_max_window_bits = this._options.serverMaxWindowBits;
|
||||
}
|
||||
if (this._options.clientMaxWindowBits) {
|
||||
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||
} else if (this._options.clientMaxWindowBits == null) {
|
||||
params.client_max_window_bits = true;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept an extension negotiation offer/response.
|
||||
*
|
||||
* @param {Array} configurations The extension negotiation offers/reponse
|
||||
* @return {Object} Accepted configuration
|
||||
* @public
|
||||
*/
|
||||
accept(configurations) {
|
||||
configurations = this.normalizeParams(configurations);
|
||||
|
||||
this.params = this._isServer
|
||||
? this.acceptAsServer(configurations)
|
||||
: this.acceptAsClient(configurations);
|
||||
|
||||
return this.params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all resources used by the extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
cleanup() {
|
||||
if (this._inflate) {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
}
|
||||
|
||||
if (this._deflate) {
|
||||
const callback = this._deflate[kCallback];
|
||||
|
||||
this._deflate.close();
|
||||
this._deflate = null;
|
||||
|
||||
if (callback) {
|
||||
callback(
|
||||
new Error(
|
||||
'The deflate stream was closed while data was being processed'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept an extension negotiation offer.
|
||||
*
|
||||
* @param {Array} offers The extension negotiation offers
|
||||
* @return {Object} Accepted configuration
|
||||
* @private
|
||||
*/
|
||||
acceptAsServer(offers) {
|
||||
const opts = this._options;
|
||||
const accepted = offers.find((params) => {
|
||||
if (
|
||||
(opts.serverNoContextTakeover === false &&
|
||||
params.server_no_context_takeover) ||
|
||||
(params.server_max_window_bits &&
|
||||
(opts.serverMaxWindowBits === false ||
|
||||
(typeof opts.serverMaxWindowBits === 'number' &&
|
||||
opts.serverMaxWindowBits > params.server_max_window_bits))) ||
|
||||
(typeof opts.clientMaxWindowBits === 'number' &&
|
||||
!params.client_max_window_bits)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!accepted) {
|
||||
throw new Error('None of the extension offers can be accepted');
|
||||
}
|
||||
|
||||
if (opts.serverNoContextTakeover) {
|
||||
accepted.server_no_context_takeover = true;
|
||||
}
|
||||
if (opts.clientNoContextTakeover) {
|
||||
accepted.client_no_context_takeover = true;
|
||||
}
|
||||
if (typeof opts.serverMaxWindowBits === 'number') {
|
||||
accepted.server_max_window_bits = opts.serverMaxWindowBits;
|
||||
}
|
||||
if (typeof opts.clientMaxWindowBits === 'number') {
|
||||
accepted.client_max_window_bits = opts.clientMaxWindowBits;
|
||||
} else if (
|
||||
accepted.client_max_window_bits === true ||
|
||||
opts.clientMaxWindowBits === false
|
||||
) {
|
||||
delete accepted.client_max_window_bits;
|
||||
}
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the extension negotiation response.
|
||||
*
|
||||
* @param {Array} response The extension negotiation response
|
||||
* @return {Object} Accepted configuration
|
||||
* @private
|
||||
*/
|
||||
acceptAsClient(response) {
|
||||
const params = response[0];
|
||||
|
||||
if (
|
||||
this._options.clientNoContextTakeover === false &&
|
||||
params.client_no_context_takeover
|
||||
) {
|
||||
throw new Error('Unexpected parameter "client_no_context_takeover"');
|
||||
}
|
||||
|
||||
if (!params.client_max_window_bits) {
|
||||
if (typeof this._options.clientMaxWindowBits === 'number') {
|
||||
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||
}
|
||||
} else if (
|
||||
this._options.clientMaxWindowBits === false ||
|
||||
(typeof this._options.clientMaxWindowBits === 'number' &&
|
||||
params.client_max_window_bits > this._options.clientMaxWindowBits)
|
||||
) {
|
||||
throw new Error(
|
||||
'Unexpected or invalid parameter "client_max_window_bits"'
|
||||
);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize parameters.
|
||||
*
|
||||
* @param {Array} configurations The extension negotiation offers/reponse
|
||||
* @return {Array} The offers/response with normalized parameters
|
||||
* @private
|
||||
*/
|
||||
normalizeParams(configurations) {
|
||||
configurations.forEach((params) => {
|
||||
Object.keys(params).forEach((key) => {
|
||||
let value = params[key];
|
||||
|
||||
if (value.length > 1) {
|
||||
throw new Error(`Parameter "${key}" must have only a single value`);
|
||||
}
|
||||
|
||||
value = value[0];
|
||||
|
||||
if (key === 'client_max_window_bits') {
|
||||
if (value !== true) {
|
||||
const num = +value;
|
||||
if (!Number.isInteger(num) || num < 8 || num > 15) {
|
||||
throw new TypeError(
|
||||
`Invalid value for parameter "${key}": ${value}`
|
||||
);
|
||||
}
|
||||
value = num;
|
||||
} else if (!this._isServer) {
|
||||
throw new TypeError(
|
||||
`Invalid value for parameter "${key}": ${value}`
|
||||
);
|
||||
}
|
||||
} else if (key === 'server_max_window_bits') {
|
||||
const num = +value;
|
||||
if (!Number.isInteger(num) || num < 8 || num > 15) {
|
||||
throw new TypeError(
|
||||
`Invalid value for parameter "${key}": ${value}`
|
||||
);
|
||||
}
|
||||
value = num;
|
||||
} else if (
|
||||
key === 'client_no_context_takeover' ||
|
||||
key === 'server_no_context_takeover'
|
||||
) {
|
||||
if (value !== true) {
|
||||
throw new TypeError(
|
||||
`Invalid value for parameter "${key}": ${value}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown parameter "${key}"`);
|
||||
}
|
||||
|
||||
params[key] = value;
|
||||
});
|
||||
});
|
||||
|
||||
return configurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress data. Concurrency limited.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @public
|
||||
*/
|
||||
decompress(data, fin, callback) {
|
||||
zlibLimiter.add((done) => {
|
||||
this._decompress(data, fin, (err, result) => {
|
||||
done();
|
||||
callback(err, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress data. Concurrency limited.
|
||||
*
|
||||
* @param {Buffer} data Data to compress
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @public
|
||||
*/
|
||||
compress(data, fin, callback) {
|
||||
zlibLimiter.add((done) => {
|
||||
this._compress(data, fin, (err, result) => {
|
||||
done();
|
||||
callback(err, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress data.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @private
|
||||
*/
|
||||
_decompress(data, fin, callback) {
|
||||
const endpoint = this._isServer ? 'client' : 'server';
|
||||
|
||||
if (!this._inflate) {
|
||||
const key = `${endpoint}_max_window_bits`;
|
||||
const windowBits =
|
||||
typeof this.params[key] !== 'number'
|
||||
? zlib.Z_DEFAULT_WINDOWBITS
|
||||
: this.params[key];
|
||||
|
||||
this._inflate = zlib.createInflateRaw({
|
||||
...this._options.zlibInflateOptions,
|
||||
windowBits
|
||||
});
|
||||
this._inflate[kPerMessageDeflate] = this;
|
||||
this._inflate[kTotalLength] = 0;
|
||||
this._inflate[kBuffers] = [];
|
||||
this._inflate.on('error', inflateOnError);
|
||||
this._inflate.on('data', inflateOnData);
|
||||
}
|
||||
|
||||
this._inflate[kCallback] = callback;
|
||||
|
||||
this._inflate.write(data);
|
||||
if (fin) this._inflate.write(TRAILER);
|
||||
|
||||
this._inflate.flush(() => {
|
||||
const err = this._inflate[kError];
|
||||
|
||||
if (err) {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = bufferUtil.concat(
|
||||
this._inflate[kBuffers],
|
||||
this._inflate[kTotalLength]
|
||||
);
|
||||
|
||||
if (this._inflate._readableState.endEmitted) {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
} else {
|
||||
this._inflate[kTotalLength] = 0;
|
||||
this._inflate[kBuffers] = [];
|
||||
|
||||
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
|
||||
this._inflate.reset();
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress data.
|
||||
*
|
||||
* @param {Buffer} data Data to compress
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @private
|
||||
*/
|
||||
_compress(data, fin, callback) {
|
||||
const endpoint = this._isServer ? 'server' : 'client';
|
||||
|
||||
if (!this._deflate) {
|
||||
const key = `${endpoint}_max_window_bits`;
|
||||
const windowBits =
|
||||
typeof this.params[key] !== 'number'
|
||||
? zlib.Z_DEFAULT_WINDOWBITS
|
||||
: this.params[key];
|
||||
|
||||
this._deflate = zlib.createDeflateRaw({
|
||||
...this._options.zlibDeflateOptions,
|
||||
windowBits
|
||||
});
|
||||
|
||||
this._deflate[kTotalLength] = 0;
|
||||
this._deflate[kBuffers] = [];
|
||||
|
||||
//
|
||||
// An `'error'` event is emitted, only on Node.js < 10.0.0, if the
|
||||
// `zlib.DeflateRaw` instance is closed while data is being processed.
|
||||
// This can happen if `PerMessageDeflate#cleanup()` is called at the wrong
|
||||
// time due to an abnormal WebSocket closure.
|
||||
//
|
||||
this._deflate.on('error', NOOP);
|
||||
this._deflate.on('data', deflateOnData);
|
||||
}
|
||||
|
||||
this._deflate[kCallback] = callback;
|
||||
|
||||
this._deflate.write(data);
|
||||
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
|
||||
if (!this._deflate) {
|
||||
//
|
||||
// The deflate stream was closed while data was being processed.
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
let data = bufferUtil.concat(
|
||||
this._deflate[kBuffers],
|
||||
this._deflate[kTotalLength]
|
||||
);
|
||||
|
||||
if (fin) data = data.slice(0, data.length - 4);
|
||||
|
||||
//
|
||||
// Ensure that the callback will not be called again in
|
||||
// `PerMessageDeflate#cleanup()`.
|
||||
//
|
||||
this._deflate[kCallback] = null;
|
||||
|
||||
this._deflate[kTotalLength] = 0;
|
||||
this._deflate[kBuffers] = [];
|
||||
|
||||
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
|
||||
this._deflate.reset();
|
||||
}
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PerMessageDeflate;
|
||||
|
||||
/**
|
||||
* The listener of the `zlib.DeflateRaw` stream `'data'` event.
|
||||
*
|
||||
* @param {Buffer} chunk A chunk of data
|
||||
* @private
|
||||
*/
|
||||
function deflateOnData(chunk) {
|
||||
this[kBuffers].push(chunk);
|
||||
this[kTotalLength] += chunk.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener of the `zlib.InflateRaw` stream `'data'` event.
|
||||
*
|
||||
* @param {Buffer} chunk A chunk of data
|
||||
* @private
|
||||
*/
|
||||
function inflateOnData(chunk) {
|
||||
this[kTotalLength] += chunk.length;
|
||||
|
||||
if (
|
||||
this[kPerMessageDeflate]._maxPayload < 1 ||
|
||||
this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
|
||||
) {
|
||||
this[kBuffers].push(chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
this[kError] = new RangeError('Max payload size exceeded');
|
||||
this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
|
||||
this[kError][kStatusCode] = 1009;
|
||||
this.removeListener('data', inflateOnData);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener of the `zlib.InflateRaw` stream `'error'` event.
|
||||
*
|
||||
* @param {Error} err The emitted error
|
||||
* @private
|
||||
*/
|
||||
function inflateOnError(err) {
|
||||
//
|
||||
// There is no need to call `Zlib#close()` as the handle is automatically
|
||||
// closed when an error is emitted.
|
||||
//
|
||||
this[kPerMessageDeflate]._inflate = null;
|
||||
err[kStatusCode] = 1007;
|
||||
this[kCallback](err);
|
||||
}
|
||||
607
node_modules/chrome-remote-interface/node_modules/ws/lib/receiver.js
generated
vendored
Normal file
607
node_modules/chrome-remote-interface/node_modules/ws/lib/receiver.js
generated
vendored
Normal file
@@ -0,0 +1,607 @@
|
||||
'use strict';
|
||||
|
||||
const { Writable } = require('stream');
|
||||
|
||||
const PerMessageDeflate = require('./permessage-deflate');
|
||||
const {
|
||||
BINARY_TYPES,
|
||||
EMPTY_BUFFER,
|
||||
kStatusCode,
|
||||
kWebSocket
|
||||
} = require('./constants');
|
||||
const { concat, toArrayBuffer, unmask } = require('./buffer-util');
|
||||
const { isValidStatusCode, isValidUTF8 } = require('./validation');
|
||||
|
||||
const GET_INFO = 0;
|
||||
const GET_PAYLOAD_LENGTH_16 = 1;
|
||||
const GET_PAYLOAD_LENGTH_64 = 2;
|
||||
const GET_MASK = 3;
|
||||
const GET_DATA = 4;
|
||||
const INFLATING = 5;
|
||||
|
||||
/**
|
||||
* HyBi Receiver implementation.
|
||||
*
|
||||
* @extends Writable
|
||||
*/
|
||||
class Receiver extends Writable {
|
||||
/**
|
||||
* Creates a Receiver instance.
|
||||
*
|
||||
* @param {String} [binaryType=nodebuffer] The type for binary data
|
||||
* @param {Object} [extensions] An object containing the negotiated extensions
|
||||
* @param {Boolean} [isServer=false] Specifies whether to operate in client or
|
||||
* server mode
|
||||
* @param {Number} [maxPayload=0] The maximum allowed message length
|
||||
*/
|
||||
constructor(binaryType, extensions, isServer, maxPayload) {
|
||||
super();
|
||||
|
||||
this._binaryType = binaryType || BINARY_TYPES[0];
|
||||
this[kWebSocket] = undefined;
|
||||
this._extensions = extensions || {};
|
||||
this._isServer = !!isServer;
|
||||
this._maxPayload = maxPayload | 0;
|
||||
|
||||
this._bufferedBytes = 0;
|
||||
this._buffers = [];
|
||||
|
||||
this._compressed = false;
|
||||
this._payloadLength = 0;
|
||||
this._mask = undefined;
|
||||
this._fragmented = 0;
|
||||
this._masked = false;
|
||||
this._fin = false;
|
||||
this._opcode = 0;
|
||||
|
||||
this._totalPayloadLength = 0;
|
||||
this._messageLength = 0;
|
||||
this._fragments = [];
|
||||
|
||||
this._state = GET_INFO;
|
||||
this._loop = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements `Writable.prototype._write()`.
|
||||
*
|
||||
* @param {Buffer} chunk The chunk of data to write
|
||||
* @param {String} encoding The character encoding of `chunk`
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
_write(chunk, encoding, cb) {
|
||||
if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
|
||||
|
||||
this._bufferedBytes += chunk.length;
|
||||
this._buffers.push(chunk);
|
||||
this.startLoop(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes `n` bytes from the buffered data.
|
||||
*
|
||||
* @param {Number} n The number of bytes to consume
|
||||
* @return {Buffer} The consumed bytes
|
||||
* @private
|
||||
*/
|
||||
consume(n) {
|
||||
this._bufferedBytes -= n;
|
||||
|
||||
if (n === this._buffers[0].length) return this._buffers.shift();
|
||||
|
||||
if (n < this._buffers[0].length) {
|
||||
const buf = this._buffers[0];
|
||||
this._buffers[0] = buf.slice(n);
|
||||
return buf.slice(0, n);
|
||||
}
|
||||
|
||||
const dst = Buffer.allocUnsafe(n);
|
||||
|
||||
do {
|
||||
const buf = this._buffers[0];
|
||||
const offset = dst.length - n;
|
||||
|
||||
if (n >= buf.length) {
|
||||
dst.set(this._buffers.shift(), offset);
|
||||
} else {
|
||||
dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
|
||||
this._buffers[0] = buf.slice(n);
|
||||
}
|
||||
|
||||
n -= buf.length;
|
||||
} while (n > 0);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the parsing loop.
|
||||
*
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
startLoop(cb) {
|
||||
let err;
|
||||
this._loop = true;
|
||||
|
||||
do {
|
||||
switch (this._state) {
|
||||
case GET_INFO:
|
||||
err = this.getInfo();
|
||||
break;
|
||||
case GET_PAYLOAD_LENGTH_16:
|
||||
err = this.getPayloadLength16();
|
||||
break;
|
||||
case GET_PAYLOAD_LENGTH_64:
|
||||
err = this.getPayloadLength64();
|
||||
break;
|
||||
case GET_MASK:
|
||||
this.getMask();
|
||||
break;
|
||||
case GET_DATA:
|
||||
err = this.getData(cb);
|
||||
break;
|
||||
default:
|
||||
// `INFLATING`
|
||||
this._loop = false;
|
||||
return;
|
||||
}
|
||||
} while (this._loop);
|
||||
|
||||
cb(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the first two bytes of a frame.
|
||||
*
|
||||
* @return {(RangeError|undefined)} A possible error
|
||||
* @private
|
||||
*/
|
||||
getInfo() {
|
||||
if (this._bufferedBytes < 2) {
|
||||
this._loop = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const buf = this.consume(2);
|
||||
|
||||
if ((buf[0] & 0x30) !== 0x00) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'RSV2 and RSV3 must be clear',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_UNEXPECTED_RSV_2_3'
|
||||
);
|
||||
}
|
||||
|
||||
const compressed = (buf[0] & 0x40) === 0x40;
|
||||
|
||||
if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'RSV1 must be clear',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_UNEXPECTED_RSV_1'
|
||||
);
|
||||
}
|
||||
|
||||
this._fin = (buf[0] & 0x80) === 0x80;
|
||||
this._opcode = buf[0] & 0x0f;
|
||||
this._payloadLength = buf[1] & 0x7f;
|
||||
|
||||
if (this._opcode === 0x00) {
|
||||
if (compressed) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'RSV1 must be clear',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_UNEXPECTED_RSV_1'
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._fragmented) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'invalid opcode 0',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_INVALID_OPCODE'
|
||||
);
|
||||
}
|
||||
|
||||
this._opcode = this._fragmented;
|
||||
} else if (this._opcode === 0x01 || this._opcode === 0x02) {
|
||||
if (this._fragmented) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
`invalid opcode ${this._opcode}`,
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_INVALID_OPCODE'
|
||||
);
|
||||
}
|
||||
|
||||
this._compressed = compressed;
|
||||
} else if (this._opcode > 0x07 && this._opcode < 0x0b) {
|
||||
if (!this._fin) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'FIN must be set',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_EXPECTED_FIN'
|
||||
);
|
||||
}
|
||||
|
||||
if (compressed) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'RSV1 must be clear',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_UNEXPECTED_RSV_1'
|
||||
);
|
||||
}
|
||||
|
||||
if (this._payloadLength > 0x7d) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
`invalid payload length ${this._payloadLength}`,
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
`invalid opcode ${this._opcode}`,
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_INVALID_OPCODE'
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
|
||||
this._masked = (buf[1] & 0x80) === 0x80;
|
||||
|
||||
if (this._isServer) {
|
||||
if (!this._masked) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'MASK must be set',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_EXPECTED_MASK'
|
||||
);
|
||||
}
|
||||
} else if (this._masked) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'MASK must be clear',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_UNEXPECTED_MASK'
|
||||
);
|
||||
}
|
||||
|
||||
if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
|
||||
else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
|
||||
else return this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extended payload length (7+16).
|
||||
*
|
||||
* @return {(RangeError|undefined)} A possible error
|
||||
* @private
|
||||
*/
|
||||
getPayloadLength16() {
|
||||
if (this._bufferedBytes < 2) {
|
||||
this._loop = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._payloadLength = this.consume(2).readUInt16BE(0);
|
||||
return this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extended payload length (7+64).
|
||||
*
|
||||
* @return {(RangeError|undefined)} A possible error
|
||||
* @private
|
||||
*/
|
||||
getPayloadLength64() {
|
||||
if (this._bufferedBytes < 8) {
|
||||
this._loop = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const buf = this.consume(8);
|
||||
const num = buf.readUInt32BE(0);
|
||||
|
||||
//
|
||||
// The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
|
||||
// if payload length is greater than this number.
|
||||
//
|
||||
if (num > Math.pow(2, 53 - 32) - 1) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'Unsupported WebSocket frame: payload length > 2^53 - 1',
|
||||
false,
|
||||
1009,
|
||||
'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'
|
||||
);
|
||||
}
|
||||
|
||||
this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
|
||||
return this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload length has been read.
|
||||
*
|
||||
* @return {(RangeError|undefined)} A possible error
|
||||
* @private
|
||||
*/
|
||||
haveLength() {
|
||||
if (this._payloadLength && this._opcode < 0x08) {
|
||||
this._totalPayloadLength += this._payloadLength;
|
||||
if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
RangeError,
|
||||
'Max payload size exceeded',
|
||||
false,
|
||||
1009,
|
||||
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._masked) this._state = GET_MASK;
|
||||
else this._state = GET_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads mask bytes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getMask() {
|
||||
if (this._bufferedBytes < 4) {
|
||||
this._loop = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._mask = this.consume(4);
|
||||
this._state = GET_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data bytes.
|
||||
*
|
||||
* @param {Function} cb Callback
|
||||
* @return {(Error|RangeError|undefined)} A possible error
|
||||
* @private
|
||||
*/
|
||||
getData(cb) {
|
||||
let data = EMPTY_BUFFER;
|
||||
|
||||
if (this._payloadLength) {
|
||||
if (this._bufferedBytes < this._payloadLength) {
|
||||
this._loop = false;
|
||||
return;
|
||||
}
|
||||
|
||||
data = this.consume(this._payloadLength);
|
||||
if (this._masked) unmask(data, this._mask);
|
||||
}
|
||||
|
||||
if (this._opcode > 0x07) return this.controlMessage(data);
|
||||
|
||||
if (this._compressed) {
|
||||
this._state = INFLATING;
|
||||
this.decompress(data, cb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length) {
|
||||
//
|
||||
// This message is not compressed so its lenght is the sum of the payload
|
||||
// length of all fragments.
|
||||
//
|
||||
this._messageLength = this._totalPayloadLength;
|
||||
this._fragments.push(data);
|
||||
}
|
||||
|
||||
return this.dataMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses data.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
decompress(data, cb) {
|
||||
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||
|
||||
perMessageDeflate.decompress(data, this._fin, (err, buf) => {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (buf.length) {
|
||||
this._messageLength += buf.length;
|
||||
if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
|
||||
return cb(
|
||||
error(
|
||||
RangeError,
|
||||
'Max payload size exceeded',
|
||||
false,
|
||||
1009,
|
||||
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this._fragments.push(buf);
|
||||
}
|
||||
|
||||
const er = this.dataMessage();
|
||||
if (er) return cb(er);
|
||||
|
||||
this.startLoop(cb);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a data message.
|
||||
*
|
||||
* @return {(Error|undefined)} A possible error
|
||||
* @private
|
||||
*/
|
||||
dataMessage() {
|
||||
if (this._fin) {
|
||||
const messageLength = this._messageLength;
|
||||
const fragments = this._fragments;
|
||||
|
||||
this._totalPayloadLength = 0;
|
||||
this._messageLength = 0;
|
||||
this._fragmented = 0;
|
||||
this._fragments = [];
|
||||
|
||||
if (this._opcode === 2) {
|
||||
let data;
|
||||
|
||||
if (this._binaryType === 'nodebuffer') {
|
||||
data = concat(fragments, messageLength);
|
||||
} else if (this._binaryType === 'arraybuffer') {
|
||||
data = toArrayBuffer(concat(fragments, messageLength));
|
||||
} else {
|
||||
data = fragments;
|
||||
}
|
||||
|
||||
this.emit('message', data);
|
||||
} else {
|
||||
const buf = concat(fragments, messageLength);
|
||||
|
||||
if (!isValidUTF8(buf)) {
|
||||
this._loop = false;
|
||||
return error(
|
||||
Error,
|
||||
'invalid UTF-8 sequence',
|
||||
true,
|
||||
1007,
|
||||
'WS_ERR_INVALID_UTF8'
|
||||
);
|
||||
}
|
||||
|
||||
this.emit('message', buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
this._state = GET_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a control message.
|
||||
*
|
||||
* @param {Buffer} data Data to handle
|
||||
* @return {(Error|RangeError|undefined)} A possible error
|
||||
* @private
|
||||
*/
|
||||
controlMessage(data) {
|
||||
if (this._opcode === 0x08) {
|
||||
this._loop = false;
|
||||
|
||||
if (data.length === 0) {
|
||||
this.emit('conclude', 1005, '');
|
||||
this.end();
|
||||
} else if (data.length === 1) {
|
||||
return error(
|
||||
RangeError,
|
||||
'invalid payload length 1',
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
|
||||
);
|
||||
} else {
|
||||
const code = data.readUInt16BE(0);
|
||||
|
||||
if (!isValidStatusCode(code)) {
|
||||
return error(
|
||||
RangeError,
|
||||
`invalid status code ${code}`,
|
||||
true,
|
||||
1002,
|
||||
'WS_ERR_INVALID_CLOSE_CODE'
|
||||
);
|
||||
}
|
||||
|
||||
const buf = data.slice(2);
|
||||
|
||||
if (!isValidUTF8(buf)) {
|
||||
return error(
|
||||
Error,
|
||||
'invalid UTF-8 sequence',
|
||||
true,
|
||||
1007,
|
||||
'WS_ERR_INVALID_UTF8'
|
||||
);
|
||||
}
|
||||
|
||||
this.emit('conclude', code, buf.toString());
|
||||
this.end();
|
||||
}
|
||||
} else if (this._opcode === 0x09) {
|
||||
this.emit('ping', data);
|
||||
} else {
|
||||
this.emit('pong', data);
|
||||
}
|
||||
|
||||
this._state = GET_INFO;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Receiver;
|
||||
|
||||
/**
|
||||
* Builds an error object.
|
||||
*
|
||||
* @param {function(new:Error|RangeError)} ErrorCtor The error constructor
|
||||
* @param {String} message The error message
|
||||
* @param {Boolean} prefix Specifies whether or not to add a default prefix to
|
||||
* `message`
|
||||
* @param {Number} statusCode The status code
|
||||
* @param {String} errorCode The exposed error code
|
||||
* @return {(Error|RangeError)} The error
|
||||
* @private
|
||||
*/
|
||||
function error(ErrorCtor, message, prefix, statusCode, errorCode) {
|
||||
const err = new ErrorCtor(
|
||||
prefix ? `Invalid WebSocket frame: ${message}` : message
|
||||
);
|
||||
|
||||
Error.captureStackTrace(err, error);
|
||||
err.code = errorCode;
|
||||
err[kStatusCode] = statusCode;
|
||||
return err;
|
||||
}
|
||||
409
node_modules/chrome-remote-interface/node_modules/ws/lib/sender.js
generated
vendored
Normal file
409
node_modules/chrome-remote-interface/node_modules/ws/lib/sender.js
generated
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
|
||||
|
||||
'use strict';
|
||||
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const { randomFillSync } = require('crypto');
|
||||
|
||||
const PerMessageDeflate = require('./permessage-deflate');
|
||||
const { EMPTY_BUFFER } = require('./constants');
|
||||
const { isValidStatusCode } = require('./validation');
|
||||
const { mask: applyMask, toBuffer } = require('./buffer-util');
|
||||
|
||||
const mask = Buffer.alloc(4);
|
||||
|
||||
/**
|
||||
* HyBi Sender implementation.
|
||||
*/
|
||||
class Sender {
|
||||
/**
|
||||
* Creates a Sender instance.
|
||||
*
|
||||
* @param {(net.Socket|tls.Socket)} socket The connection socket
|
||||
* @param {Object} [extensions] An object containing the negotiated extensions
|
||||
*/
|
||||
constructor(socket, extensions) {
|
||||
this._extensions = extensions || {};
|
||||
this._socket = socket;
|
||||
|
||||
this._firstFragment = true;
|
||||
this._compress = false;
|
||||
|
||||
this._bufferedBytes = 0;
|
||||
this._deflating = false;
|
||||
this._queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames a piece of data according to the HyBi WebSocket protocol.
|
||||
*
|
||||
* @param {Buffer} data The data to frame
|
||||
* @param {Object} options Options object
|
||||
* @param {Number} options.opcode The opcode
|
||||
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
|
||||
* modified
|
||||
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
|
||||
* FIN bit
|
||||
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
|
||||
* `data`
|
||||
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
|
||||
* RSV1 bit
|
||||
* @return {Buffer[]} The framed data as a list of `Buffer` instances
|
||||
* @public
|
||||
*/
|
||||
static frame(data, options) {
|
||||
const merge = options.mask && options.readOnly;
|
||||
let offset = options.mask ? 6 : 2;
|
||||
let payloadLength = data.length;
|
||||
|
||||
if (data.length >= 65536) {
|
||||
offset += 8;
|
||||
payloadLength = 127;
|
||||
} else if (data.length > 125) {
|
||||
offset += 2;
|
||||
payloadLength = 126;
|
||||
}
|
||||
|
||||
const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
|
||||
|
||||
target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
|
||||
if (options.rsv1) target[0] |= 0x40;
|
||||
|
||||
target[1] = payloadLength;
|
||||
|
||||
if (payloadLength === 126) {
|
||||
target.writeUInt16BE(data.length, 2);
|
||||
} else if (payloadLength === 127) {
|
||||
target.writeUInt32BE(0, 2);
|
||||
target.writeUInt32BE(data.length, 6);
|
||||
}
|
||||
|
||||
if (!options.mask) return [target, data];
|
||||
|
||||
randomFillSync(mask, 0, 4);
|
||||
|
||||
target[1] |= 0x80;
|
||||
target[offset - 4] = mask[0];
|
||||
target[offset - 3] = mask[1];
|
||||
target[offset - 2] = mask[2];
|
||||
target[offset - 1] = mask[3];
|
||||
|
||||
if (merge) {
|
||||
applyMask(data, mask, target, offset, data.length);
|
||||
return [target];
|
||||
}
|
||||
|
||||
applyMask(data, mask, data, 0, data.length);
|
||||
return [target, data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a close message to the other peer.
|
||||
*
|
||||
* @param {Number} [code] The status code component of the body
|
||||
* @param {String} [data] The message component of the body
|
||||
* @param {Boolean} [mask=false] Specifies whether or not to mask the message
|
||||
* @param {Function} [cb] Callback
|
||||
* @public
|
||||
*/
|
||||
close(code, data, mask, cb) {
|
||||
let buf;
|
||||
|
||||
if (code === undefined) {
|
||||
buf = EMPTY_BUFFER;
|
||||
} else if (typeof code !== 'number' || !isValidStatusCode(code)) {
|
||||
throw new TypeError('First argument must be a valid error code number');
|
||||
} else if (data === undefined || data === '') {
|
||||
buf = Buffer.allocUnsafe(2);
|
||||
buf.writeUInt16BE(code, 0);
|
||||
} else {
|
||||
const length = Buffer.byteLength(data);
|
||||
|
||||
if (length > 123) {
|
||||
throw new RangeError('The message must not be greater than 123 bytes');
|
||||
}
|
||||
|
||||
buf = Buffer.allocUnsafe(2 + length);
|
||||
buf.writeUInt16BE(code, 0);
|
||||
buf.write(data, 2);
|
||||
}
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.doClose, buf, mask, cb]);
|
||||
} else {
|
||||
this.doClose(buf, mask, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a close message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
|
||||
* @param {Function} [cb] Callback
|
||||
* @private
|
||||
*/
|
||||
doClose(data, mask, cb) {
|
||||
this.sendFrame(
|
||||
Sender.frame(data, {
|
||||
fin: true,
|
||||
rsv1: false,
|
||||
opcode: 0x08,
|
||||
mask,
|
||||
readOnly: false
|
||||
}),
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a ping message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
|
||||
* @param {Function} [cb] Callback
|
||||
* @public
|
||||
*/
|
||||
ping(data, mask, cb) {
|
||||
const buf = toBuffer(data);
|
||||
|
||||
if (buf.length > 125) {
|
||||
throw new RangeError('The data size must not be greater than 125 bytes');
|
||||
}
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]);
|
||||
} else {
|
||||
this.doPing(buf, mask, toBuffer.readOnly, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a ping message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
|
||||
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
|
||||
* @param {Function} [cb] Callback
|
||||
* @private
|
||||
*/
|
||||
doPing(data, mask, readOnly, cb) {
|
||||
this.sendFrame(
|
||||
Sender.frame(data, {
|
||||
fin: true,
|
||||
rsv1: false,
|
||||
opcode: 0x09,
|
||||
mask,
|
||||
readOnly
|
||||
}),
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a pong message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
|
||||
* @param {Function} [cb] Callback
|
||||
* @public
|
||||
*/
|
||||
pong(data, mask, cb) {
|
||||
const buf = toBuffer(data);
|
||||
|
||||
if (buf.length > 125) {
|
||||
throw new RangeError('The data size must not be greater than 125 bytes');
|
||||
}
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]);
|
||||
} else {
|
||||
this.doPong(buf, mask, toBuffer.readOnly, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a pong message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
|
||||
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
|
||||
* @param {Function} [cb] Callback
|
||||
* @private
|
||||
*/
|
||||
doPong(data, mask, readOnly, cb) {
|
||||
this.sendFrame(
|
||||
Sender.frame(data, {
|
||||
fin: true,
|
||||
rsv1: false,
|
||||
opcode: 0x0a,
|
||||
mask,
|
||||
readOnly
|
||||
}),
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a data message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Object} options Options object
|
||||
* @param {Boolean} [options.compress=false] Specifies whether or not to
|
||||
* compress `data`
|
||||
* @param {Boolean} [options.binary=false] Specifies whether `data` is binary
|
||||
* or text
|
||||
* @param {Boolean} [options.fin=false] Specifies whether the fragment is the
|
||||
* last one
|
||||
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
|
||||
* `data`
|
||||
* @param {Function} [cb] Callback
|
||||
* @public
|
||||
*/
|
||||
send(data, options, cb) {
|
||||
const buf = toBuffer(data);
|
||||
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||
let opcode = options.binary ? 2 : 1;
|
||||
let rsv1 = options.compress;
|
||||
|
||||
if (this._firstFragment) {
|
||||
this._firstFragment = false;
|
||||
if (rsv1 && perMessageDeflate) {
|
||||
rsv1 = buf.length >= perMessageDeflate._threshold;
|
||||
}
|
||||
this._compress = rsv1;
|
||||
} else {
|
||||
rsv1 = false;
|
||||
opcode = 0;
|
||||
}
|
||||
|
||||
if (options.fin) this._firstFragment = true;
|
||||
|
||||
if (perMessageDeflate) {
|
||||
const opts = {
|
||||
fin: options.fin,
|
||||
rsv1,
|
||||
opcode,
|
||||
mask: options.mask,
|
||||
readOnly: toBuffer.readOnly
|
||||
};
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.dispatch, buf, this._compress, opts, cb]);
|
||||
} else {
|
||||
this.dispatch(buf, this._compress, opts, cb);
|
||||
}
|
||||
} else {
|
||||
this.sendFrame(
|
||||
Sender.frame(buf, {
|
||||
fin: options.fin,
|
||||
rsv1: false,
|
||||
opcode,
|
||||
mask: options.mask,
|
||||
readOnly: toBuffer.readOnly
|
||||
}),
|
||||
cb
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a data message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Boolean} [compress=false] Specifies whether or not to compress
|
||||
* `data`
|
||||
* @param {Object} options Options object
|
||||
* @param {Number} options.opcode The opcode
|
||||
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
|
||||
* modified
|
||||
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
|
||||
* FIN bit
|
||||
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
|
||||
* `data`
|
||||
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
|
||||
* RSV1 bit
|
||||
* @param {Function} [cb] Callback
|
||||
* @private
|
||||
*/
|
||||
dispatch(data, compress, options, cb) {
|
||||
if (!compress) {
|
||||
this.sendFrame(Sender.frame(data, options), cb);
|
||||
return;
|
||||
}
|
||||
|
||||
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||
|
||||
this._bufferedBytes += data.length;
|
||||
this._deflating = true;
|
||||
perMessageDeflate.compress(data, options.fin, (_, buf) => {
|
||||
if (this._socket.destroyed) {
|
||||
const err = new Error(
|
||||
'The socket was closed while data was being compressed'
|
||||
);
|
||||
|
||||
if (typeof cb === 'function') cb(err);
|
||||
|
||||
for (let i = 0; i < this._queue.length; i++) {
|
||||
const callback = this._queue[i][4];
|
||||
|
||||
if (typeof callback === 'function') callback(err);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._bufferedBytes -= data.length;
|
||||
this._deflating = false;
|
||||
options.readOnly = false;
|
||||
this.sendFrame(Sender.frame(buf, options), cb);
|
||||
this.dequeue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes queued send operations.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
dequeue() {
|
||||
while (!this._deflating && this._queue.length) {
|
||||
const params = this._queue.shift();
|
||||
|
||||
this._bufferedBytes -= params[1].length;
|
||||
Reflect.apply(params[0], this, params.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a send operation.
|
||||
*
|
||||
* @param {Array} params Send operation parameters.
|
||||
* @private
|
||||
*/
|
||||
enqueue(params) {
|
||||
this._bufferedBytes += params[1].length;
|
||||
this._queue.push(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a frame.
|
||||
*
|
||||
* @param {Buffer[]} list The frame to send
|
||||
* @param {Function} [cb] Callback
|
||||
* @private
|
||||
*/
|
||||
sendFrame(list, cb) {
|
||||
if (list.length === 2) {
|
||||
this._socket.cork();
|
||||
this._socket.write(list[0]);
|
||||
this._socket.write(list[1], cb);
|
||||
this._socket.uncork();
|
||||
} else {
|
||||
this._socket.write(list[0], cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sender;
|
||||
180
node_modules/chrome-remote-interface/node_modules/ws/lib/stream.js
generated
vendored
Normal file
180
node_modules/chrome-remote-interface/node_modules/ws/lib/stream.js
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
'use strict';
|
||||
|
||||
const { Duplex } = require('stream');
|
||||
|
||||
/**
|
||||
* Emits the `'close'` event on a stream.
|
||||
*
|
||||
* @param {Duplex} stream The stream.
|
||||
* @private
|
||||
*/
|
||||
function emitClose(stream) {
|
||||
stream.emit('close');
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener of the `'end'` event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function duplexOnEnd() {
|
||||
if (!this.destroyed && this._writableState.finished) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener of the `'error'` event.
|
||||
*
|
||||
* @param {Error} err The error
|
||||
* @private
|
||||
*/
|
||||
function duplexOnError(err) {
|
||||
this.removeListener('error', duplexOnError);
|
||||
this.destroy();
|
||||
if (this.listenerCount('error') === 0) {
|
||||
// Do not suppress the throwing behavior.
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a `WebSocket` in a duplex stream.
|
||||
*
|
||||
* @param {WebSocket} ws The `WebSocket` to wrap
|
||||
* @param {Object} [options] The options for the `Duplex` constructor
|
||||
* @return {Duplex} The duplex stream
|
||||
* @public
|
||||
*/
|
||||
function createWebSocketStream(ws, options) {
|
||||
let resumeOnReceiverDrain = true;
|
||||
let terminateOnDestroy = true;
|
||||
|
||||
function receiverOnDrain() {
|
||||
if (resumeOnReceiverDrain) ws._socket.resume();
|
||||
}
|
||||
|
||||
if (ws.readyState === ws.CONNECTING) {
|
||||
ws.once('open', function open() {
|
||||
ws._receiver.removeAllListeners('drain');
|
||||
ws._receiver.on('drain', receiverOnDrain);
|
||||
});
|
||||
} else {
|
||||
ws._receiver.removeAllListeners('drain');
|
||||
ws._receiver.on('drain', receiverOnDrain);
|
||||
}
|
||||
|
||||
const duplex = new Duplex({
|
||||
...options,
|
||||
autoDestroy: false,
|
||||
emitClose: false,
|
||||
objectMode: false,
|
||||
writableObjectMode: false
|
||||
});
|
||||
|
||||
ws.on('message', function message(msg) {
|
||||
if (!duplex.push(msg)) {
|
||||
resumeOnReceiverDrain = false;
|
||||
ws._socket.pause();
|
||||
}
|
||||
});
|
||||
|
||||
ws.once('error', function error(err) {
|
||||
if (duplex.destroyed) return;
|
||||
|
||||
// Prevent `ws.terminate()` from being called by `duplex._destroy()`.
|
||||
//
|
||||
// - If the `'error'` event is emitted before the `'open'` event, then
|
||||
// `ws.terminate()` is a noop as no socket is assigned.
|
||||
// - Otherwise, the error is re-emitted by the listener of the `'error'`
|
||||
// event of the `Receiver` object. The listener already closes the
|
||||
// connection by calling `ws.close()`. This allows a close frame to be
|
||||
// sent to the other peer. If `ws.terminate()` is called right after this,
|
||||
// then the close frame might not be sent.
|
||||
terminateOnDestroy = false;
|
||||
duplex.destroy(err);
|
||||
});
|
||||
|
||||
ws.once('close', function close() {
|
||||
if (duplex.destroyed) return;
|
||||
|
||||
duplex.push(null);
|
||||
});
|
||||
|
||||
duplex._destroy = function (err, callback) {
|
||||
if (ws.readyState === ws.CLOSED) {
|
||||
callback(err);
|
||||
process.nextTick(emitClose, duplex);
|
||||
return;
|
||||
}
|
||||
|
||||
let called = false;
|
||||
|
||||
ws.once('error', function error(err) {
|
||||
called = true;
|
||||
callback(err);
|
||||
});
|
||||
|
||||
ws.once('close', function close() {
|
||||
if (!called) callback(err);
|
||||
process.nextTick(emitClose, duplex);
|
||||
});
|
||||
|
||||
if (terminateOnDestroy) ws.terminate();
|
||||
};
|
||||
|
||||
duplex._final = function (callback) {
|
||||
if (ws.readyState === ws.CONNECTING) {
|
||||
ws.once('open', function open() {
|
||||
duplex._final(callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If the value of the `_socket` property is `null` it means that `ws` is a
|
||||
// client websocket and the handshake failed. In fact, when this happens, a
|
||||
// socket is never assigned to the websocket. Wait for the `'error'` event
|
||||
// that will be emitted by the websocket.
|
||||
if (ws._socket === null) return;
|
||||
|
||||
if (ws._socket._writableState.finished) {
|
||||
callback();
|
||||
if (duplex._readableState.endEmitted) duplex.destroy();
|
||||
} else {
|
||||
ws._socket.once('finish', function finish() {
|
||||
// `duplex` is not destroyed here because the `'end'` event will be
|
||||
// emitted on `duplex` after this `'finish'` event. The EOF signaling
|
||||
// `null` chunk is, in fact, pushed when the websocket emits `'close'`.
|
||||
callback();
|
||||
});
|
||||
ws.close();
|
||||
}
|
||||
};
|
||||
|
||||
duplex._read = function () {
|
||||
if (
|
||||
(ws.readyState === ws.OPEN || ws.readyState === ws.CLOSING) &&
|
||||
!resumeOnReceiverDrain
|
||||
) {
|
||||
resumeOnReceiverDrain = true;
|
||||
if (!ws._receiver._writableState.needDrain) ws._socket.resume();
|
||||
}
|
||||
};
|
||||
|
||||
duplex._write = function (chunk, encoding, callback) {
|
||||
if (ws.readyState === ws.CONNECTING) {
|
||||
ws.once('open', function open() {
|
||||
duplex._write(chunk, encoding, callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
ws.send(chunk, callback);
|
||||
};
|
||||
|
||||
duplex.on('end', duplexOnEnd);
|
||||
duplex.on('error', duplexOnError);
|
||||
return duplex;
|
||||
}
|
||||
|
||||
module.exports = createWebSocketStream;
|
||||
104
node_modules/chrome-remote-interface/node_modules/ws/lib/validation.js
generated
vendored
Normal file
104
node_modules/chrome-remote-interface/node_modules/ws/lib/validation.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Checks if a status code is allowed in a close frame.
|
||||
*
|
||||
* @param {Number} code The status code
|
||||
* @return {Boolean} `true` if the status code is valid, else `false`
|
||||
* @public
|
||||
*/
|
||||
function isValidStatusCode(code) {
|
||||
return (
|
||||
(code >= 1000 &&
|
||||
code <= 1014 &&
|
||||
code !== 1004 &&
|
||||
code !== 1005 &&
|
||||
code !== 1006) ||
|
||||
(code >= 3000 && code <= 4999)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given buffer contains only correct UTF-8.
|
||||
* Ported from https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c by
|
||||
* Markus Kuhn.
|
||||
*
|
||||
* @param {Buffer} buf The buffer to check
|
||||
* @return {Boolean} `true` if `buf` contains only correct UTF-8, else `false`
|
||||
* @public
|
||||
*/
|
||||
function _isValidUTF8(buf) {
|
||||
const len = buf.length;
|
||||
let i = 0;
|
||||
|
||||
while (i < len) {
|
||||
if ((buf[i] & 0x80) === 0) {
|
||||
// 0xxxxxxx
|
||||
i++;
|
||||
} else if ((buf[i] & 0xe0) === 0xc0) {
|
||||
// 110xxxxx 10xxxxxx
|
||||
if (
|
||||
i + 1 === len ||
|
||||
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||||
(buf[i] & 0xfe) === 0xc0 // Overlong
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i += 2;
|
||||
} else if ((buf[i] & 0xf0) === 0xe0) {
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
if (
|
||||
i + 2 >= len ||
|
||||
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||||
(buf[i + 2] & 0xc0) !== 0x80 ||
|
||||
(buf[i] === 0xe0 && (buf[i + 1] & 0xe0) === 0x80) || // Overlong
|
||||
(buf[i] === 0xed && (buf[i + 1] & 0xe0) === 0xa0) // Surrogate (U+D800 - U+DFFF)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i += 3;
|
||||
} else if ((buf[i] & 0xf8) === 0xf0) {
|
||||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if (
|
||||
i + 3 >= len ||
|
||||
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||||
(buf[i + 2] & 0xc0) !== 0x80 ||
|
||||
(buf[i + 3] & 0xc0) !== 0x80 ||
|
||||
(buf[i] === 0xf0 && (buf[i + 1] & 0xf0) === 0x80) || // Overlong
|
||||
(buf[i] === 0xf4 && buf[i + 1] > 0x8f) ||
|
||||
buf[i] > 0xf4 // > U+10FFFF
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
let isValidUTF8 = require('utf-8-validate');
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (typeof isValidUTF8 === 'object') {
|
||||
isValidUTF8 = isValidUTF8.Validation.isValidUTF8; // utf-8-validate@<3.0.0
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isValidStatusCode,
|
||||
isValidUTF8(buf) {
|
||||
return buf.length < 150 ? _isValidUTF8(buf) : isValidUTF8(buf);
|
||||
}
|
||||
};
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
module.exports = {
|
||||
isValidStatusCode,
|
||||
isValidUTF8: _isValidUTF8
|
||||
};
|
||||
}
|
||||
449
node_modules/chrome-remote-interface/node_modules/ws/lib/websocket-server.js
generated
vendored
Normal file
449
node_modules/chrome-remote-interface/node_modules/ws/lib/websocket-server.js
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls|https$" }] */
|
||||
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const { createHash } = require('crypto');
|
||||
|
||||
const PerMessageDeflate = require('./permessage-deflate');
|
||||
const WebSocket = require('./websocket');
|
||||
const { format, parse } = require('./extension');
|
||||
const { GUID, kWebSocket } = require('./constants');
|
||||
|
||||
const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
|
||||
|
||||
const RUNNING = 0;
|
||||
const CLOSING = 1;
|
||||
const CLOSED = 2;
|
||||
|
||||
/**
|
||||
* Class representing a WebSocket server.
|
||||
*
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class WebSocketServer extends EventEmitter {
|
||||
/**
|
||||
* Create a `WebSocketServer` instance.
|
||||
*
|
||||
* @param {Object} options Configuration options
|
||||
* @param {Number} [options.backlog=511] The maximum length of the queue of
|
||||
* pending connections
|
||||
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to
|
||||
* track clients
|
||||
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
||||
* @param {String} [options.host] The hostname where to bind the server
|
||||
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
||||
* size
|
||||
* @param {Boolean} [options.noServer=false] Enable no server mode
|
||||
* @param {String} [options.path] Accept only connections matching this path
|
||||
* @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable
|
||||
* permessage-deflate
|
||||
* @param {Number} [options.port] The port where to bind the server
|
||||
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
|
||||
* server to use
|
||||
* @param {Function} [options.verifyClient] A hook to reject connections
|
||||
* @param {Function} [callback] A listener for the `listening` event
|
||||
*/
|
||||
constructor(options, callback) {
|
||||
super();
|
||||
|
||||
options = {
|
||||
maxPayload: 100 * 1024 * 1024,
|
||||
perMessageDeflate: false,
|
||||
handleProtocols: null,
|
||||
clientTracking: true,
|
||||
verifyClient: null,
|
||||
noServer: false,
|
||||
backlog: null, // use default (511 as implemented in net.js)
|
||||
server: null,
|
||||
host: null,
|
||||
path: null,
|
||||
port: null,
|
||||
...options
|
||||
};
|
||||
|
||||
if (
|
||||
(options.port == null && !options.server && !options.noServer) ||
|
||||
(options.port != null && (options.server || options.noServer)) ||
|
||||
(options.server && options.noServer)
|
||||
) {
|
||||
throw new TypeError(
|
||||
'One and only one of the "port", "server", or "noServer" options ' +
|
||||
'must be specified'
|
||||
);
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
this._server = http.createServer((req, res) => {
|
||||
const body = http.STATUS_CODES[426];
|
||||
|
||||
res.writeHead(426, {
|
||||
'Content-Length': body.length,
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
res.end(body);
|
||||
});
|
||||
this._server.listen(
|
||||
options.port,
|
||||
options.host,
|
||||
options.backlog,
|
||||
callback
|
||||
);
|
||||
} else if (options.server) {
|
||||
this._server = options.server;
|
||||
}
|
||||
|
||||
if (this._server) {
|
||||
const emitConnection = this.emit.bind(this, 'connection');
|
||||
|
||||
this._removeListeners = addListeners(this._server, {
|
||||
listening: this.emit.bind(this, 'listening'),
|
||||
error: this.emit.bind(this, 'error'),
|
||||
upgrade: (req, socket, head) => {
|
||||
this.handleUpgrade(req, socket, head, emitConnection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (options.perMessageDeflate === true) options.perMessageDeflate = {};
|
||||
if (options.clientTracking) this.clients = new Set();
|
||||
this.options = options;
|
||||
this._state = RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound address, the address family name, and port of the server
|
||||
* as reported by the operating system if listening on an IP socket.
|
||||
* If the server is listening on a pipe or UNIX domain socket, the name is
|
||||
* returned as a string.
|
||||
*
|
||||
* @return {(Object|String|null)} The address of the server
|
||||
* @public
|
||||
*/
|
||||
address() {
|
||||
if (this.options.noServer) {
|
||||
throw new Error('The server is operating in "noServer" mode');
|
||||
}
|
||||
|
||||
if (!this._server) return null;
|
||||
return this._server.address();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the server.
|
||||
*
|
||||
* @param {Function} [cb] Callback
|
||||
* @public
|
||||
*/
|
||||
close(cb) {
|
||||
if (cb) this.once('close', cb);
|
||||
|
||||
if (this._state === CLOSED) {
|
||||
process.nextTick(emitClose, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._state === CLOSING) return;
|
||||
this._state = CLOSING;
|
||||
|
||||
//
|
||||
// Terminate all associated clients.
|
||||
//
|
||||
if (this.clients) {
|
||||
for (const client of this.clients) client.terminate();
|
||||
}
|
||||
|
||||
const server = this._server;
|
||||
|
||||
if (server) {
|
||||
this._removeListeners();
|
||||
this._removeListeners = this._server = null;
|
||||
|
||||
//
|
||||
// Close the http server if it was internally created.
|
||||
//
|
||||
if (this.options.port != null) {
|
||||
server.close(emitClose.bind(undefined, this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
process.nextTick(emitClose, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* See if a given request should be handled by this server instance.
|
||||
*
|
||||
* @param {http.IncomingMessage} req Request object to inspect
|
||||
* @return {Boolean} `true` if the request is valid, else `false`
|
||||
* @public
|
||||
*/
|
||||
shouldHandle(req) {
|
||||
if (this.options.path) {
|
||||
const index = req.url.indexOf('?');
|
||||
const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
|
||||
|
||||
if (pathname !== this.options.path) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a HTTP Upgrade request.
|
||||
*
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {(net.Socket|tls.Socket)} socket The network socket between the
|
||||
* server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
handleUpgrade(req, socket, head, cb) {
|
||||
socket.on('error', socketOnError);
|
||||
|
||||
const key =
|
||||
req.headers['sec-websocket-key'] !== undefined
|
||||
? req.headers['sec-websocket-key'].trim()
|
||||
: false;
|
||||
const upgrade = req.headers.upgrade;
|
||||
const version = +req.headers['sec-websocket-version'];
|
||||
const extensions = {};
|
||||
|
||||
if (
|
||||
req.method !== 'GET' ||
|
||||
upgrade === undefined ||
|
||||
upgrade.toLowerCase() !== 'websocket' ||
|
||||
!key ||
|
||||
!keyRegex.test(key) ||
|
||||
(version !== 8 && version !== 13) ||
|
||||
!this.shouldHandle(req)
|
||||
) {
|
||||
return abortHandshake(socket, 400);
|
||||
}
|
||||
|
||||
if (this.options.perMessageDeflate) {
|
||||
const perMessageDeflate = new PerMessageDeflate(
|
||||
this.options.perMessageDeflate,
|
||||
true,
|
||||
this.options.maxPayload
|
||||
);
|
||||
|
||||
try {
|
||||
const offers = parse(req.headers['sec-websocket-extensions']);
|
||||
|
||||
if (offers[PerMessageDeflate.extensionName]) {
|
||||
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
|
||||
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||||
}
|
||||
} catch (err) {
|
||||
return abortHandshake(socket, 400);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Optionally call external client verification handler.
|
||||
//
|
||||
if (this.options.verifyClient) {
|
||||
const info = {
|
||||
origin:
|
||||
req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
|
||||
secure: !!(req.socket.authorized || req.socket.encrypted),
|
||||
req
|
||||
};
|
||||
|
||||
if (this.options.verifyClient.length === 2) {
|
||||
this.options.verifyClient(info, (verified, code, message, headers) => {
|
||||
if (!verified) {
|
||||
return abortHandshake(socket, code || 401, message, headers);
|
||||
}
|
||||
|
||||
this.completeUpgrade(key, extensions, req, socket, head, cb);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
|
||||
}
|
||||
|
||||
this.completeUpgrade(key, extensions, req, socket, head, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade the connection to WebSocket.
|
||||
*
|
||||
* @param {String} key The value of the `Sec-WebSocket-Key` header
|
||||
* @param {Object} extensions The accepted extensions
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {(net.Socket|tls.Socket)} socket The network socket between the
|
||||
* server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @throws {Error} If called more than once with the same socket
|
||||
* @private
|
||||
*/
|
||||
completeUpgrade(key, extensions, req, socket, head, cb) {
|
||||
//
|
||||
// Destroy the socket if the client has already sent a FIN packet.
|
||||
//
|
||||
if (!socket.readable || !socket.writable) return socket.destroy();
|
||||
|
||||
if (socket[kWebSocket]) {
|
||||
throw new Error(
|
||||
'server.handleUpgrade() was called more than once with the same ' +
|
||||
'socket, possibly due to a misconfiguration'
|
||||
);
|
||||
}
|
||||
|
||||
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
||||
|
||||
const digest = createHash('sha1')
|
||||
.update(key + GUID)
|
||||
.digest('base64');
|
||||
|
||||
const headers = [
|
||||
'HTTP/1.1 101 Switching Protocols',
|
||||
'Upgrade: websocket',
|
||||
'Connection: Upgrade',
|
||||
`Sec-WebSocket-Accept: ${digest}`
|
||||
];
|
||||
|
||||
const ws = new WebSocket(null);
|
||||
let protocol = req.headers['sec-websocket-protocol'];
|
||||
|
||||
if (protocol) {
|
||||
protocol = protocol.split(',').map(trim);
|
||||
|
||||
//
|
||||
// Optionally call external protocol selection handler.
|
||||
//
|
||||
if (this.options.handleProtocols) {
|
||||
protocol = this.options.handleProtocols(protocol, req);
|
||||
} else {
|
||||
protocol = protocol[0];
|
||||
}
|
||||
|
||||
if (protocol) {
|
||||
headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
|
||||
ws._protocol = protocol;
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions[PerMessageDeflate.extensionName]) {
|
||||
const params = extensions[PerMessageDeflate.extensionName].params;
|
||||
const value = format({
|
||||
[PerMessageDeflate.extensionName]: [params]
|
||||
});
|
||||
headers.push(`Sec-WebSocket-Extensions: ${value}`);
|
||||
ws._extensions = extensions;
|
||||
}
|
||||
|
||||
//
|
||||
// Allow external modification/inspection of handshake headers.
|
||||
//
|
||||
this.emit('headers', headers, req);
|
||||
|
||||
socket.write(headers.concat('\r\n').join('\r\n'));
|
||||
socket.removeListener('error', socketOnError);
|
||||
|
||||
ws.setSocket(socket, head, this.options.maxPayload);
|
||||
|
||||
if (this.clients) {
|
||||
this.clients.add(ws);
|
||||
ws.on('close', () => this.clients.delete(ws));
|
||||
}
|
||||
|
||||
cb(ws, req);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketServer;
|
||||
|
||||
/**
|
||||
* Add event listeners on an `EventEmitter` using a map of <event, listener>
|
||||
* pairs.
|
||||
*
|
||||
* @param {EventEmitter} server The event emitter
|
||||
* @param {Object.<String, Function>} map The listeners to add
|
||||
* @return {Function} A function that will remove the added listeners when
|
||||
* called
|
||||
* @private
|
||||
*/
|
||||
function addListeners(server, map) {
|
||||
for (const event of Object.keys(map)) server.on(event, map[event]);
|
||||
|
||||
return function removeListeners() {
|
||||
for (const event of Object.keys(map)) {
|
||||
server.removeListener(event, map[event]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a `'close'` event on an `EventEmitter`.
|
||||
*
|
||||
* @param {EventEmitter} server The event emitter
|
||||
* @private
|
||||
*/
|
||||
function emitClose(server) {
|
||||
server._state = CLOSED;
|
||||
server.emit('close');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle premature socket errors.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function socketOnError() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection when preconditions are not fulfilled.
|
||||
*
|
||||
* @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
|
||||
* @param {Number} code The HTTP response status code
|
||||
* @param {String} [message] The HTTP response body
|
||||
* @param {Object} [headers] Additional HTTP response headers
|
||||
* @private
|
||||
*/
|
||||
function abortHandshake(socket, code, message, headers) {
|
||||
if (socket.writable) {
|
||||
message = message || http.STATUS_CODES[code];
|
||||
headers = {
|
||||
Connection: 'close',
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Length': Buffer.byteLength(message),
|
||||
...headers
|
||||
};
|
||||
|
||||
socket.write(
|
||||
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
|
||||
Object.keys(headers)
|
||||
.map((h) => `${h}: ${headers[h]}`)
|
||||
.join('\r\n') +
|
||||
'\r\n\r\n' +
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
socket.removeListener('error', socketOnError);
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace characters from both ends of a string.
|
||||
*
|
||||
* @param {String} str The string
|
||||
* @return {String} A new string representing `str` stripped of whitespace
|
||||
* characters from both its beginning and end
|
||||
* @private
|
||||
*/
|
||||
function trim(str) {
|
||||
return str.trim();
|
||||
}
|
||||
1197
node_modules/chrome-remote-interface/node_modules/ws/lib/websocket.js
generated
vendored
Normal file
1197
node_modules/chrome-remote-interface/node_modules/ws/lib/websocket.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
56
node_modules/chrome-remote-interface/node_modules/ws/package.json
generated
vendored
Normal file
56
node_modules/chrome-remote-interface/node_modules/ws/package.json
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "ws",
|
||||
"version": "7.5.10",
|
||||
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
|
||||
"keywords": [
|
||||
"HyBi",
|
||||
"Push",
|
||||
"RFC-6455",
|
||||
"WebSocket",
|
||||
"WebSockets",
|
||||
"real-time"
|
||||
],
|
||||
"homepage": "https://github.com/websockets/ws",
|
||||
"bugs": "https://github.com/websockets/ws/issues",
|
||||
"repository": "websockets/ws",
|
||||
"author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"browser": "browser.js",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"files": [
|
||||
"browser.js",
|
||||
"index.js",
|
||||
"lib/*.js"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js",
|
||||
"integration": "mocha --throw-deprecation test/*.integration.js",
|
||||
"lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"bufferutil": "^4.0.1",
|
||||
"eslint": "^7.2.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"mocha": "^7.0.0",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
}
|
||||
}
|
||||
64
node_modules/chrome-remote-interface/package.json
generated
vendored
Normal file
64
node_modules/chrome-remote-interface/package.json
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "chrome-remote-interface",
|
||||
"author": "Andrea Cardaci <cyrus.and@gmail.com>",
|
||||
"license": "MIT",
|
||||
"contributors": [
|
||||
"Andrey Sidorov <sidoares@yandex.ru>",
|
||||
"Greg Cochard <greg@gregcochard.com>"
|
||||
],
|
||||
"description": "Chrome Debugging Protocol interface",
|
||||
"keywords": [
|
||||
"chrome",
|
||||
"debug",
|
||||
"protocol",
|
||||
"remote",
|
||||
"interface"
|
||||
],
|
||||
"homepage": "https://github.com/cyrus-and/chrome-remote-interface",
|
||||
"version": "0.34.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/cyrus-and/chrome-remote-interface.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/cyrus-and/chrome-remote-interface/issues"
|
||||
},
|
||||
"engine-strict": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "2.11.x",
|
||||
"ws": "^7.2.0"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"bin",
|
||||
"index.js",
|
||||
"chrome-remote-interface.js",
|
||||
"webpack.config.js"
|
||||
],
|
||||
"bin": {
|
||||
"chrome-remote-interface": "bin/client.js"
|
||||
},
|
||||
"main": "index.js",
|
||||
"browser": "chrome-remote-interface.js",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "8.x.x",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"babel-preset-env": "^0.0.0",
|
||||
"eslint": "^8.8.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^11.1.0",
|
||||
"process": "^0.11.10",
|
||||
"url": "^0.11.0",
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.39.0",
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./scripts/run-tests.sh",
|
||||
"webpack": "webpack",
|
||||
"prepare": "webpack"
|
||||
}
|
||||
}
|
||||
48
node_modules/chrome-remote-interface/webpack.config.js
generated
vendored
Normal file
48
node_modules/chrome-remote-interface/webpack.config.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
function criWrapper(_, options, callback) {
|
||||
window.criRequest(options, callback); // eslint-disable-line no-undef
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
resolve: {
|
||||
fallback: {
|
||||
'util': require.resolve('util/'),
|
||||
'url': require.resolve('url/'),
|
||||
'http': false,
|
||||
'https': false,
|
||||
'dns': false
|
||||
},
|
||||
alias: {
|
||||
'ws': './websocket-wrapper.js'
|
||||
}
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
'./external-request.js': `var (${criWrapper})`
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
})
|
||||
],
|
||||
},
|
||||
entry: ['babel-polyfill', './index.js'],
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'chrome-remote-interface.js',
|
||||
libraryTarget: process.env.TARGET || 'commonjs2',
|
||||
library: 'CDP'
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user