pw-repl 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -0
- package/LICENSE.md +21 -0
- package/README.md +132 -0
- package/bin/pw-repl.js +128 -0
- package/lib/client.js +78 -0
- package/lib/commands.js +1370 -0
- package/lib/help.js +210 -0
- package/lib/output.js +70 -0
- package/lib/runner.js +163 -0
- package/lib/send.js +127 -0
- package/lib/server.js +91 -0
- package/lib/start.js +117 -0
- package/lib/state.js +61 -0
- package/package.json +41 -0
- package/skill/SKILL.md +102 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Agent Instructions — playwright-repl
|
|
2
|
+
|
|
3
|
+
How to use the REPL is in `skill/SKILL.md` (the same text `pw-repl skill` prints). Read it first. In
|
|
4
|
+
this clone, `pw-repl` there means `bin/pw-repl.js` (from the folder this file is in) when it is not
|
|
5
|
+
installed globally.
|
|
6
|
+
|
|
7
|
+
## Contributing
|
|
8
|
+
|
|
9
|
+
If you hit a limitation or write a workaround, consider adding the capability to the REPL instead.
|
|
10
|
+
|
|
11
|
+
- A command is a function in the `commands` object in `lib/commands.js`, plus an entry in `lib/help.js`
|
|
12
|
+
under one topic.
|
|
13
|
+
- `bin/pw-repl.js` is the command line; `lib/start.js` connects and runs the prompt; `lib/runner.js`
|
|
14
|
+
runs commands one at a time; `lib/server.js` is the opt-in server; `lib/send.js` and `lib/client.js`
|
|
15
|
+
are `send` and `where`; `lib/output.js` routes all output so the server can return it.
|
|
16
|
+
- Comment the *why* when it isn't obvious from the code.
|
|
17
|
+
- `npm test` runs the suite (about 40s): a private headless Chromium, a local test site, and the real
|
|
18
|
+
REPL with its server. Nothing is mocked, and the shared browser is never touched. It finds Chromium
|
|
19
|
+
through `PW_TEST_CHROME` or Playwright's installed browsers, and skips the browser tests if there is
|
|
20
|
+
none. Add a test with each new command or behaviour.
|
|
21
|
+
- For anything the tests can't reach, exercise the change in a running REPL. Use your own tmux session
|
|
22
|
+
and a new tab, not the user's.
|
|
23
|
+
- `skill/SKILL.md` is how agents learn to use the REPL: keep it in step with a change to how it is
|
|
24
|
+
used, and leave its Custom rules section empty.
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 arcanemachine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# playwright-repl
|
|
2
|
+
|
|
3
|
+
A text REPL for driving an existing Chromium through Playwright over CDP. You and agents can share the same
|
|
4
|
+
browser: people click in it, and the REPL inspects it, fakes responses, and records what happened.
|
|
5
|
+
|
|
6
|
+
## Getting started
|
|
7
|
+
|
|
8
|
+
### Prerequisites
|
|
9
|
+
|
|
10
|
+
- Node.js 20 or newer (`npm test` needs 21 or newer).
|
|
11
|
+
- A Chromium-based browser (Chrome, Chromium, ungoogled-chromium, ...).
|
|
12
|
+
- Optional: tmux, to use `pw-repl send` without the command server.
|
|
13
|
+
|
|
14
|
+
### 1. Start the browser with remote debugging
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.config/chrome-debug"
|
|
18
|
+
# or: chromium --remote-debugging-port=9222 --user-data-dir="$HOME/.config/chromium-debug"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use a separate `--user-data-dir`: recent Chrome versions do not open the debugging port on the default
|
|
22
|
+
profile. Check it is up with `curl http://localhost:9222/json/version`.
|
|
23
|
+
|
|
24
|
+
### 2. Install and start the REPL
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g pw-repl
|
|
28
|
+
pw-repl run # connects to localhost:9222; pw-repl on its own shows the usage
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Without installing: `npx pw-repl` (subcommands work the same way).
|
|
32
|
+
From a clone: `npm install`, then `bin/pw-repl.js run` (or `npm link` to get `pw-repl`).
|
|
33
|
+
|
|
34
|
+
It lists the open tabs and shows a `pw>` prompt. To send it commands from scripts or agents later
|
|
35
|
+
(`pw-repl send`), run it in a tmux session named `playwright-repl` (`tmux new -s playwright-repl`), or
|
|
36
|
+
start it with `pw-repl serve` instead, which needs no tmux.
|
|
37
|
+
|
|
38
|
+
### 3. Try it
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
pw> help # common tasks and topics; help <topic>, help <command>, help --all
|
|
42
|
+
pw> tab new https://example.com # open your own tab to work in
|
|
43
|
+
pw> snapshot # the page by role and name, with [ref=eN] labels
|
|
44
|
+
pw> click aria-ref=e3 # click by label (or any Playwright selector)
|
|
45
|
+
pw> requests # requests the tab made
|
|
46
|
+
pw> tab close
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Commands act on the selected tab (`tab` lists the tabs, with `*` on the selected one).
|
|
50
|
+
|
|
51
|
+
## Common tasks
|
|
52
|
+
|
|
53
|
+
| I want to… | Commands |
|
|
54
|
+
| ------------------------------------ | --------------------------------------------------------------- |
|
|
55
|
+
| see where I am | `tab`, `info` |
|
|
56
|
+
| see what is on the page | `snapshot`, `screenshot` |
|
|
57
|
+
| do something on it | `click`, `fill`, `press` |
|
|
58
|
+
| see what the page requested | `requests`, then `body <#>` for what one got back |
|
|
59
|
+
| see console messages and errors | `console` |
|
|
60
|
+
| show an agent what I do | `watch on`, click around in the browser, then `watch` |
|
|
61
|
+
| see each step as I click | `watch on --live` |
|
|
62
|
+
| record requests and console together | `capture on`, then `capture off` |
|
|
63
|
+
| break the backend on purpose | `route <glob> <status> <json>` (fake a response), `network off` |
|
|
64
|
+
| clean up | `modes off` |
|
|
65
|
+
|
|
66
|
+
Everything else is in `help <topic>`; `help <command>` has usage and caveats.
|
|
67
|
+
|
|
68
|
+
### Modes
|
|
69
|
+
|
|
70
|
+
`watch`, `capture`, `route` and `network off` stay on until you turn them off: `watch on|off`,
|
|
71
|
+
`capture on|off`, `route ...|route off`, `network off|on`. While any are on in the selected tab, the prompt
|
|
72
|
+
shows them: `(watch network:off routes:2) pw>`. `modes` lists them for every tab, and `modes off` turns them
|
|
73
|
+
all off.
|
|
74
|
+
|
|
75
|
+
`tab`, `watch`, `capture`, `route`, `network` and `modes` on their own show their state and what you can
|
|
76
|
+
run next.
|
|
77
|
+
|
|
78
|
+
## Options
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pw-repl run http://localhost:3000 # connect and navigate tab [0] to a URL
|
|
82
|
+
pw-repl serve # also accept commands on /tmp/playwright-repl.sock (prompt: pw[serve]>)
|
|
83
|
+
PW_CDP_URL=http://host:9222 pw-repl run # a browser elsewhere
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Browser on the host, REPL in a container:** with host networking, `localhost:9222` reaches the host's
|
|
87
|
+
browser directly. Otherwise set `PW_CDP_URL` to the host's address as seen from the container (for example
|
|
88
|
+
the Docker bridge gateway, `ip route | awk '/default/ {print $3}'`).
|
|
89
|
+
|
|
90
|
+
## From scripts and agents
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pw-repl send info
|
|
94
|
+
pw-repl send help # the REPL's commands; works without a running REPL
|
|
95
|
+
pw-repl where # which REPL send would reach
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`pw-repl send` sends one command and prints its result, through the server when it is running and through the
|
|
99
|
+
`playwright-repl` tmux session otherwise. The server speaks HTTP with JSON:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
curl --unix-socket /tmp/playwright-repl.sock -H 'Content-Type: application/json' \
|
|
103
|
+
-d '{"command": "info"}' http://localhost/run
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It returns `{"status": "ok" | "error", "output": "..."}`, plus `"unconfirmed": true` when the command may or
|
|
107
|
+
may not have done what it was sent to do (it timed out, or the REPL quit while it ran and it was not
|
|
108
|
+
read-only). It is off unless started with `pw-repl serve`.
|
|
109
|
+
`pw-repl serve <port>` serves TCP on 127.0.0.1 instead; other addresses are refused.
|
|
110
|
+
|
|
111
|
+
### A skill for agents
|
|
112
|
+
|
|
113
|
+
`pw-repl skill` prints an agent skill (`SKILL.md`) that teaches an agent to use the REPL: how to start
|
|
114
|
+
it, send commands, and share the browser with a person. Save it as `pw-repl/SKILL.md` in the folder
|
|
115
|
+
your agent reads skills from:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
mkdir -p <skills folder>/pw-repl
|
|
119
|
+
pw-repl skill > <skills folder>/pw-repl/SKILL.md
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The same text is in `skill/SKILL.md`. Working on the REPL itself: see `AGENTS.md`.
|
|
123
|
+
|
|
124
|
+
## Tests
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm test
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Runs against a private headless Chromium it starts itself (via `PW_TEST_CHROME`, or Playwright's installed
|
|
131
|
+
browsers: `npx playwright-core install chromium`) and a local test site; the browser tests are skipped when no
|
|
132
|
+
Chromium is found.
|
package/bin/pw-repl.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The one command: pw-repl run | serve | send | where | help | skill.
|
|
3
|
+
|
|
4
|
+
const { looksLikeEndpoint } = require('../lib/client');
|
|
5
|
+
|
|
6
|
+
const USAGE = `Usage:
|
|
7
|
+
pw-repl run [start-url]
|
|
8
|
+
connect to the browser and open the prompt
|
|
9
|
+
|
|
10
|
+
pw-repl serve [endpoint] [start-url]
|
|
11
|
+
the same, plus a command server (socket, port, or 127.0.0.1:port; default /tmp/playwright-repl.sock)
|
|
12
|
+
|
|
13
|
+
pw-repl send [-e endpoint | -s session] [-t seconds] <command...>
|
|
14
|
+
run one command in a running REPL and print its output
|
|
15
|
+
|
|
16
|
+
pw-repl where [-e endpoint | -s session]
|
|
17
|
+
say which REPL send would reach
|
|
18
|
+
|
|
19
|
+
pw-repl help [topic | command | --all]
|
|
20
|
+
this usage; with a topic or command, the REPL's help for it
|
|
21
|
+
|
|
22
|
+
pw-repl skill
|
|
23
|
+
print an agent skill (SKILL.md) that teaches an agent to use the REPL
|
|
24
|
+
|
|
25
|
+
send uses the server when -e, $PW_ENDPOINT, or the socket ($PW_SOCKET, default /tmp/playwright-repl.sock)
|
|
26
|
+
is there, and the tmux session (-s, $PW_TMUX_SESSION, default playwright-repl) otherwise. If the socket
|
|
27
|
+
exists but nothing answers, send fails rather than fall back.
|
|
28
|
+
|
|
29
|
+
send exit status: 0 ok, 1 command error, 2 completion not confirmed, 64 usage or unreachable.
|
|
30
|
+
|
|
31
|
+
The browser must be running with --remote-debugging-port (default http://localhost:9222; set $PW_CDP_URL).`;
|
|
32
|
+
|
|
33
|
+
const REPL_HELP = `The REPL's own commands: help at the pw> prompt, or pw-repl send help here (no REPL needed).
|
|
34
|
+
|
|
35
|
+
pw-repl help <topic | command | --all> shows one part of it.`;
|
|
36
|
+
|
|
37
|
+
function usage() {
|
|
38
|
+
console.error(USAGE);
|
|
39
|
+
process.exit(64);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// -e, -s and -t, then the command words (unquoted words are one command).
|
|
43
|
+
function parseSendArgs(args, allowCommand) {
|
|
44
|
+
const options = { endpoint: null, session: null, timeout: 20, command: '' };
|
|
45
|
+
let i = 0;
|
|
46
|
+
for (; i < args.length; i++) {
|
|
47
|
+
const arg = args[i];
|
|
48
|
+
if (arg === '-e' || arg === '-s' || arg === '-t') {
|
|
49
|
+
const value = args[++i];
|
|
50
|
+
if (value === undefined) usage();
|
|
51
|
+
if (arg === '-e') options.endpoint = value;
|
|
52
|
+
if (arg === '-s') options.session = value;
|
|
53
|
+
if (arg === '-t') {
|
|
54
|
+
if (!/^\d+$/.test(value) || Number(value) < 1) usage();
|
|
55
|
+
options.timeout = Number(value);
|
|
56
|
+
}
|
|
57
|
+
} else if (arg === '--') { i++; break; } else break;
|
|
58
|
+
}
|
|
59
|
+
const words = args.slice(i);
|
|
60
|
+
if (!allowCommand && words.length) usage();
|
|
61
|
+
options.command = words.join(' ').trim();
|
|
62
|
+
return options;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function startOptions(args, serve) {
|
|
66
|
+
const options = { serve, endpoint: null, startUrl: null };
|
|
67
|
+
const rest = [...args];
|
|
68
|
+
if (serve && rest[0] && looksLikeEndpoint(rest[0])) options.endpoint = rest.shift();
|
|
69
|
+
if (rest.length > 1 || (rest[0] && rest[0].startsWith('-'))) usage();
|
|
70
|
+
options.startUrl = rest[0] || null;
|
|
71
|
+
return options;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function help(topic) {
|
|
75
|
+
const helpText = require('../lib/help');
|
|
76
|
+
const text = helpText.render(topic);
|
|
77
|
+
if (text === null) {
|
|
78
|
+
console.log(`Error: No help for ${topic}. Topics: ${Object.keys(helpText.TOPICS).join(', ')}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
console.log(text);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Printed as is, to be saved as a skill; the hint goes to the terminal only.
|
|
85
|
+
function skill() {
|
|
86
|
+
process.stdout.write(require('fs').readFileSync(require('path').join(__dirname, '..', 'skill', 'SKILL.md'), 'utf8'));
|
|
87
|
+
if (process.stdout.isTTY) console.error('\nSave it as pw-repl/SKILL.md in the folder your agent reads skills from:\npw-repl skill > <skills folder>/pw-repl/SKILL.md');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function main() {
|
|
91
|
+
let [subcommand, ...args] = process.argv.slice(2);
|
|
92
|
+
// Bare pw-repl explains itself rather than connect to someone's browser.
|
|
93
|
+
if (subcommand === undefined) return console.log(`${USAGE}\n\n${REPL_HELP}`);
|
|
94
|
+
switch (subcommand) {
|
|
95
|
+
case 'run':
|
|
96
|
+
case 'serve':
|
|
97
|
+
return require('../lib/start').start(startOptions(args, subcommand === 'serve'));
|
|
98
|
+
case 'send': {
|
|
99
|
+
const options = parseSendArgs(args, true);
|
|
100
|
+
// help is fixed text, so it needs no browser: answer it here.
|
|
101
|
+
const asksHelp = /^help(?:\s+(.*))?$/.exec(options.command);
|
|
102
|
+
if (asksHelp) return help((asksHelp[1] || '').trim());
|
|
103
|
+
process.exit(await require('../lib/send').send(options));
|
|
104
|
+
}
|
|
105
|
+
// falls through never: process.exit above
|
|
106
|
+
case 'where':
|
|
107
|
+
process.exit(await require('../lib/send').where(parseSendArgs(args, false)));
|
|
108
|
+
// falls through never
|
|
109
|
+
case 'help': {
|
|
110
|
+
const topic = args.join(' ').trim();
|
|
111
|
+
if (topic && topic !== '-h' && topic !== '--help') return help(topic);
|
|
112
|
+
return console.log(`${USAGE}\n\n${REPL_HELP}`);
|
|
113
|
+
}
|
|
114
|
+
case 'skill':
|
|
115
|
+
if (args.length) usage();
|
|
116
|
+
return skill();
|
|
117
|
+
case '-v':
|
|
118
|
+
case '--version':
|
|
119
|
+
return console.log(require('../package.json').version);
|
|
120
|
+
case '-h':
|
|
121
|
+
case '--help':
|
|
122
|
+
return console.log(`${USAGE}\n\n${REPL_HELP}`);
|
|
123
|
+
default:
|
|
124
|
+
return usage();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
main();
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// How to reach a REPL's command server, and one request to it.
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_SOCKET = '/tmp/playwright-repl.sock';
|
|
6
|
+
const LOOPBACK = new Set(['127.0.0.1', 'localhost', '::1']);
|
|
7
|
+
|
|
8
|
+
// A port or loopback host:port is TCP; anything else is a unix socket path.
|
|
9
|
+
function parseEndpoint(value) {
|
|
10
|
+
if (!value) return { socket: DEFAULT_SOCKET };
|
|
11
|
+
if (/^\d+$/.test(value)) return { host: '127.0.0.1', port: Number(value) };
|
|
12
|
+
const tcp = /^\[?([^\]/]+?)\]?:(\d+)$/.exec(value);
|
|
13
|
+
if (tcp) {
|
|
14
|
+
if (!LOOPBACK.has(tcp[1])) throw new Error(`Refusing non-loopback address ${tcp[1]}; use 127.0.0.1, localhost, or ::1`);
|
|
15
|
+
return { host: tcp[1], port: Number(tcp[2]) };
|
|
16
|
+
}
|
|
17
|
+
return { socket: path.resolve(value) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Tells an endpoint argument apart from a start URL on the command line.
|
|
21
|
+
function looksLikeEndpoint(value) {
|
|
22
|
+
if (value.includes('://')) return false;
|
|
23
|
+
return /^\d+$/.test(value) || /:\d+$/.test(value) || value.endsWith('.sock') || value.startsWith('/') || value.startsWith('.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function describe(endpoint) {
|
|
27
|
+
return endpoint.socket || `${endpoint.host}:${endpoint.port}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function target(endpoint) {
|
|
31
|
+
return endpoint.socket ? { socketPath: endpoint.socket } : { host: endpoint.host, port: endpoint.port };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// GET /health: does not go through the command queue or show in the pane.
|
|
35
|
+
function health(endpoint, timeoutMs) {
|
|
36
|
+
return new Promise(resolve => {
|
|
37
|
+
const req = http.get({ ...target(endpoint), path: '/health', timeout: timeoutMs }, res => {
|
|
38
|
+
res.resume();
|
|
39
|
+
resolve(res.statusCode === 200);
|
|
40
|
+
});
|
|
41
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
42
|
+
req.on('error', () => resolve(false));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// One command. Resolves to { result }, { timeout: true }, { dropped: reason }
|
|
47
|
+
// (the connection closed after the command was sent, so it may have run) or
|
|
48
|
+
// { unreachable: reason } (it was never sent).
|
|
49
|
+
function request(endpoint, command, timeoutMs) {
|
|
50
|
+
return new Promise(resolve => {
|
|
51
|
+
const body = JSON.stringify({ command });
|
|
52
|
+
let connected = false;
|
|
53
|
+
const req = http.request({
|
|
54
|
+
...target(endpoint),
|
|
55
|
+
path: '/run',
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
58
|
+
}, res => {
|
|
59
|
+
let text = '';
|
|
60
|
+
res.setEncoding('utf8');
|
|
61
|
+
res.on('close', () => { if (!res.complete) resolve({ dropped: 'the answer was cut off' }); });
|
|
62
|
+
res.on('data', chunk => { text += chunk; });
|
|
63
|
+
res.on('end', () => {
|
|
64
|
+
try { resolve({ result: JSON.parse(text) }); }
|
|
65
|
+
catch { resolve({ timeout: true }); }
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
req.setTimeout(timeoutMs, () => { req.destroy(); resolve({ timeout: true }); });
|
|
69
|
+
req.on('socket', socket => socket.once('connect', () => { connected = true; }));
|
|
70
|
+
req.on('error', error => {
|
|
71
|
+
const reason = error.code || error.message;
|
|
72
|
+
resolve(connected ? { dropped: reason } : { unreachable: reason });
|
|
73
|
+
});
|
|
74
|
+
req.end(body);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { DEFAULT_SOCKET, parseEndpoint, looksLikeEndpoint, describe, health, request };
|