@maschinenlesbar.org/pegel-online-cli 0.0.2 → 0.0.3
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/README.md +169 -130
- package/dist/src/cli/program.js +2 -2
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/cli/shared.d.ts +9 -0
- package/dist/src/cli/shared.d.ts.map +1 -1
- package/dist/src/cli/shared.js +21 -0
- package/dist/src/cli/shared.js.map +1 -1
- package/dist/src/client/engine.d.ts +9 -0
- package/dist/src/client/engine.d.ts.map +1 -1
- package/dist/src/client/engine.js +56 -5
- package/dist/src/client/engine.js.map +1 -1
- package/dist/src/client/http.d.ts +0 -5
- package/dist/src/client/http.d.ts.map +1 -1
- package/dist/src/client/http.js +38 -4
- package/dist/src/client/http.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,192 +1,231 @@
|
|
|
1
1
|
# pegel-online-cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[
|
|
5
|
-
(
|
|
6
|
-
Schifffahrtsverwaltung des Bundes) — live **water levels** and related timeseries
|
|
7
|
-
across the German federal waterway network.
|
|
8
|
-
|
|
9
|
-
- **Zero runtime HTTP dependencies** — built on Node's built-in `http`/`https` (no axios, no fetch polyfill).
|
|
10
|
-
- **One small dependency** for the CLI: [`commander`](https://github.com/tj/commander.js).
|
|
11
|
-
- **Strongly typed** — typed stations, timeseries and measurement shapes.
|
|
12
|
-
- **Well tested** — unit tests on Node's built-in test runner (`node --test`), every HTTP response mocked.
|
|
13
|
-
- **Read-only, no auth** — the PEGELONLINE API needs no key; this client only reads.
|
|
3
|
+
[](https://github.com/maschinenlesbar-org/pegel-online-cli/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/maschinenlesbar-org/pegel-online-cli/actions/workflows/release.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@maschinenlesbar.org/pegel-online-cli)
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
Check live water levels and gauge readings for any German federal waterway station
|
|
8
|
+
from your terminal. `pegel` is a command-line tool over the open
|
|
9
|
+
[PEGELONLINE REST API v2](https://www.pegelonline.wsv.de/webservice/dokuRestapi)
|
|
10
|
+
(`pegelonline.wsv.de`) operated by the WSV — find stations, query current readings,
|
|
11
|
+
pull measurement histories, and get the full picture on any gauge — as clean JSON
|
|
12
|
+
you can pipe straight into [`jq`](https://jqlang.github.io/jq/).
|
|
18
13
|
|
|
19
|
-
|
|
14
|
+
- **Works out of the box** — no account, no API key, no configuration. Install and query.
|
|
15
|
+
- **Clean JSON output** — pretty-printed by default, `--compact` for one-line/scripting.
|
|
16
|
+
- **Covers the full hierarchy** — bodies of water, stations, timeseries, current readings, measurement windows, and gauge marks.
|
|
17
|
+
- **Live data** — readings update continuously from hundreds of federal gauges across Germany's rivers and canals.
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
> Want to use this as a TypeScript library or understand how it's built?
|
|
20
|
+
> See **[DEVELOPING.md](DEVELOPING.md)**.
|
|
22
21
|
|
|
23
22
|
## Install
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
|
-
npm
|
|
27
|
-
npm run build # compiles TypeScript to dist/
|
|
25
|
+
npm i -g @maschinenlesbar.org/pegel-online-cli
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
This installs the **`pegel`** command. Requires **Node.js 20+**.
|
|
29
|
+
|
|
30
|
+
Check it works:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
node dist/src/cli/index.js --help
|
|
34
|
-
# or, after `npm link` / global install:
|
|
35
33
|
pegel --help
|
|
36
34
|
```
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
## Quickstart
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
No setup needed — the API is public and requires no key. Your first command:
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`WT` (water temperature), `LT` (air temperature), depending on the station.
|
|
40
|
+
```bash
|
|
41
|
+
pegel waters
|
|
42
|
+
```
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
This lists every body of water (*Gewässer*) in the network. Grab just the shortnames
|
|
45
|
+
with `jq`:
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
| `--timeout <ms>` | Per-request timeout (default `30000`) |
|
|
53
|
-
| `--user-agent <ua>` | `User-Agent` header value |
|
|
54
|
-
| `--max-retries <n>` | Retries for transient `429`/`503` responses (default `2`) |
|
|
55
|
-
| `--max-response-bytes <n>` | Cap response body size in bytes (`0` = unlimited; default 100 MiB) |
|
|
56
|
-
| `--compact` | Print JSON on a single line |
|
|
47
|
+
```bash
|
|
48
|
+
pegel waters | jq -r '.[].shortname'
|
|
49
|
+
```
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
`pegel --compact waters` and `pegel waters --compact` work; placing them before
|
|
60
|
-
the command is recommended for clarity.
|
|
51
|
+
Pick one — say `RHEIN` — and get the current water level at Bonn:
|
|
61
52
|
|
|
62
|
-
|
|
53
|
+
```bash
|
|
54
|
+
pegel current BONN
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pull just the value and timestamp:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pegel current BONN | jq '{value, timestamp}'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
63
64
|
|
|
64
65
|
```text
|
|
65
|
-
stations list
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
waters list all bodies of water (Gewässer)
|
|
66
|
+
stations list [filters…] list / filter stations
|
|
67
|
+
stations get <station> [includes…] full details for one station
|
|
68
|
+
timeseries <station> [timeseries] metadata for a timeseries
|
|
69
|
+
current <station> [timeseries] the current measurement
|
|
70
|
+
measurements <station> [timeseries] a window of measurements
|
|
71
|
+
waters list all bodies of water (Gewässer)
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
A `<station>` may be a **uuid**, **number**, **shortname** or **longname** — e.g.
|
|
75
|
+
`BONN`, `6302010`, or a full UUID. A `[timeseries]` defaults to **`W`** (water
|
|
76
|
+
level); other codes include `Q` (flow/discharge), `WT` (water temperature), and
|
|
77
|
+
`LT` (air temperature) depending on the station.
|
|
78
|
+
|
|
79
|
+
### `stations list` filters
|
|
80
|
+
|
|
81
|
+
| Flag | Meaning |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `--ids <id>` | station id (uuid/number/shortname/longname); repeatable |
|
|
84
|
+
| `--waters <shortname>` | filter by water shortname (see `waters`) |
|
|
85
|
+
| `--fuzzy-id <id>` | fuzzy match against short/long name |
|
|
86
|
+
| `--include-timeseries` | embed each station's timeseries list |
|
|
87
|
+
| `--include-current` | embed the current measurement |
|
|
88
|
+
| `--include-characteristic` | embed characteristic (gauge-mark) values |
|
|
89
|
+
|
|
90
|
+
### `stations get` options
|
|
91
|
+
|
|
92
|
+
| Flag | Meaning |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| `--include-timeseries` | embed the station's timeseries list |
|
|
95
|
+
| `--include-current` | embed the current measurement |
|
|
96
|
+
| `--include-characteristic` | embed characteristic (gauge-mark) values |
|
|
97
|
+
|
|
98
|
+
### `measurements` options
|
|
99
|
+
|
|
100
|
+
| Flag | Meaning |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `--start <iso>` | window start — ISO-8601 instant *or* a period like `P7D` |
|
|
103
|
+
| `--end <iso>` | window end — ISO-8601 instant |
|
|
104
|
+
|
|
105
|
+
The **[Glossary](GLOSSARY.md)** explains every domain term and timeseries code.
|
|
106
|
+
|
|
107
|
+
## Common tasks
|
|
108
|
+
|
|
109
|
+
A few recipes to get going — see **[Usage.md](Usage.md)** for the full,
|
|
110
|
+
use-case-driven set.
|
|
75
111
|
|
|
76
112
|
```bash
|
|
77
113
|
# All stations on the Rhine, with their current water level
|
|
78
114
|
pegel stations list --waters RHEIN --include-current
|
|
79
115
|
|
|
80
|
-
# One station
|
|
116
|
+
# One station — metadata + timeseries list + current reading
|
|
81
117
|
pegel stations get BONN --include-timeseries --include-current
|
|
82
118
|
|
|
83
|
-
# Current water level at Bonn
|
|
119
|
+
# Current water level at Bonn (default timeseries W)
|
|
84
120
|
pegel current BONN
|
|
85
121
|
|
|
86
|
-
#
|
|
87
|
-
pegel
|
|
122
|
+
# Current flow at Bonn (timeseries Q)
|
|
123
|
+
pegel current BONN Q
|
|
88
124
|
|
|
89
|
-
#
|
|
90
|
-
pegel
|
|
125
|
+
# Last 7 days of measurements
|
|
126
|
+
pegel measurements BONN W --start P7D
|
|
91
127
|
|
|
92
|
-
#
|
|
93
|
-
pegel
|
|
94
|
-
```
|
|
128
|
+
# Explicit date window
|
|
129
|
+
pegel measurements BONN W --start 2026-06-01T00:00:00Z --end 2026-06-07T00:00:00Z
|
|
95
130
|
|
|
96
|
-
|
|
131
|
+
# Gauge marks (MNW/MHW/NSW/HSW) for Cologne
|
|
132
|
+
pegel stations get KÖLN --include-timeseries --include-characteristic
|
|
97
133
|
|
|
98
|
-
|
|
134
|
+
# Timeseries metadata — discover which series a station exposes
|
|
135
|
+
pegel timeseries BONN
|
|
99
136
|
|
|
100
|
-
|
|
137
|
+
# Multiple specific stations in one call
|
|
138
|
+
pegel stations list --ids BONN --ids KÖLN --ids EMMERICH --include-current
|
|
139
|
+
```
|
|
101
140
|
|
|
102
|
-
|
|
103
|
-
import { PegelOnlineClient, PegelApiError } from "@maschinenlesbar.org/pegel-online-cli";
|
|
141
|
+
## Output & scripting
|
|
104
142
|
|
|
105
|
-
|
|
143
|
+
Every command prints **pretty JSON to stdout**. Errors and diagnostics go to
|
|
144
|
+
stderr, so piping stdout into `jq` stays clean.
|
|
106
145
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const series = await client.timeseries.measurements("BONN", "W", { start: "P3D" });
|
|
146
|
+
```bash
|
|
147
|
+
# Water shortnames, one per line
|
|
148
|
+
pegel waters | jq -r '.[].shortname'
|
|
111
149
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} catch (err) {
|
|
115
|
-
if (err instanceof PegelApiError) console.error(err.status, err.detail);
|
|
116
|
-
}
|
|
117
|
-
```
|
|
150
|
+
# Reshape a current measurement
|
|
151
|
+
pegel current BONN | jq '{value, timestamp}'
|
|
118
152
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
new PegelOnlineClient({
|
|
123
|
-
baseUrl: "https://www.pegelonline.wsv.de",
|
|
124
|
-
timeoutMs: 15_000,
|
|
125
|
-
maxRetries: 3, // 429 / 503 are retried with linear backoff
|
|
126
|
-
maxResponseBytes: 50 << 20, // abort responses larger than 50 MiB (0 = unlimited)
|
|
127
|
-
userAgent: "my-app/1.0",
|
|
128
|
-
transport: customTransport, // inject your own HTTP transport
|
|
129
|
-
});
|
|
130
|
-
```
|
|
153
|
+
# CSV-ish series for a spreadsheet
|
|
154
|
+
pegel measurements BONN W --start P3D | jq -r '.[] | [.timestamp, .value] | @csv'
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
# Station names and coordinates on the Rhine (tab-separated)
|
|
157
|
+
pegel stations list --waters RHEIN | jq -r '.[] | [.shortname, .longitude, .latitude] | @tsv'
|
|
133
158
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
---
|
|
159
|
+
# Gauge marks for the W series at Cologne
|
|
160
|
+
pegel stations get KÖLN --include-timeseries --include-characteristic \
|
|
161
|
+
| jq '.timeseries[] | select(.shortname == "W") | .characteristicValues'
|
|
162
|
+
```
|
|
139
163
|
|
|
140
|
-
|
|
164
|
+
Use `--compact` for single-line JSON in pipelines and logs:
|
|
141
165
|
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
client/
|
|
145
|
-
types.ts # Station / TimeseriesInfo / CurrentMeasurement / Measurement + param objects
|
|
146
|
-
query.ts # dependency-free query-string builder
|
|
147
|
-
http.ts # the Transport interface + default node:http/https transport
|
|
148
|
-
engine.ts # URL building, retry/backoff, redirects, JSON decoding, error mapping
|
|
149
|
-
errors.ts # PegelError / PegelApiError / PegelNetworkError / PegelParseError
|
|
150
|
-
client.ts # PegelOnlineClient — stations + timeseries resources over the engine
|
|
151
|
-
cli/
|
|
152
|
-
io.ts # injectable I/O seam (stdout/stderr)
|
|
153
|
-
shared.ts # option parsers, global-option resolver, JSON renderer
|
|
154
|
-
commands/ # stations + timeseries/measurements/waters
|
|
155
|
-
program.ts # assembles the commander program from injectable deps
|
|
156
|
-
run.ts # parses argv -> exit code (no process.exit; testable)
|
|
157
|
-
index.ts # #! bin shim
|
|
166
|
+
```bash
|
|
167
|
+
pegel --compact current BONN | jq '.value'
|
|
158
168
|
```
|
|
159
169
|
|
|
160
|
-
**
|
|
170
|
+
`--compact` (and every global option) works **before or after** the command —
|
|
171
|
+
both `pegel --compact waters` and `pegel waters --compact` do the same thing.
|
|
161
172
|
|
|
162
|
-
|
|
163
|
-
uses `node:http`/`node:https`; tests inject a mock. This keeps the client free of any HTTP framework.
|
|
164
|
-
- The CLI is built around injectable `CliDeps` (client factory + I/O), so the whole program can be
|
|
165
|
-
driven in-process by tests with a mocked client and captured output — no subprocesses.
|
|
166
|
-
- The engine follows HTTP redirects, so trailing-slash and host normalisations are handled transparently.
|
|
173
|
+
**Exit codes** make the CLI easy to use in scripts:
|
|
167
174
|
|
|
168
|
-
|
|
175
|
+
| Code | Meaning |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `0` | success (also `--help` / `--version`) |
|
|
178
|
+
| `2` | bad usage / invalid argument (nothing was sent) |
|
|
179
|
+
| `4` | station or resource not found (`404`) |
|
|
180
|
+
| `1` | any other error (network, timeout, unexpected response) |
|
|
181
|
+
|
|
182
|
+
## Troubleshooting
|
|
183
|
+
|
|
184
|
+
- **`command not found: pegel`** — the global npm bin directory isn't on your
|
|
185
|
+
`PATH`. Run `npm bin -g` to find it and add it, or run via
|
|
186
|
+
`npx @maschinenlesbar.org/pegel-online-cli …`.
|
|
187
|
+
- **Exit `2` / "invalid argument"** — check the command syntax: a `<station>`
|
|
188
|
+
argument is required, and `--start` / `--end` must be valid ISO-8601 instants or
|
|
189
|
+
periods (e.g. `P7D`). Run `pegel <command> --help` for the exact signature.
|
|
190
|
+
- **Exit `4` / "not found"** — the station shortname or id doesn't exist. Run
|
|
191
|
+
`pegel stations list --fuzzy-id <name>` or `pegel waters` to find the right
|
|
192
|
+
shortname.
|
|
193
|
+
- **Exit `1` / network error** — connectivity, DNS, or a timeout. Try again, or
|
|
194
|
+
raise the limit with `--timeout 60000`.
|
|
195
|
+
- **Empty `timeseries` array** — the station doesn't publish the requested series.
|
|
196
|
+
Run `pegel timeseries <station>` to see which codes it actually exposes.
|
|
197
|
+
|
|
198
|
+
## Global options
|
|
199
|
+
|
|
200
|
+
These apply to every command and may be given before *or* after it:
|
|
169
201
|
|
|
170
|
-
|
|
202
|
+
| Option | Description |
|
|
203
|
+
| --- | --- |
|
|
204
|
+
| `-V, --version` | Print the version number |
|
|
205
|
+
| `-h, --help` | Show help for the program or a command |
|
|
206
|
+
| `--compact` | Print JSON on a single line instead of pretty-printed |
|
|
207
|
+
| `--base-url <url>` | API base URL (default `https://www.pegelonline.wsv.de`) |
|
|
208
|
+
| `--timeout <ms>` | Per-request timeout in milliseconds (default `30000`) |
|
|
209
|
+
| `--user-agent <ua>` | `User-Agent` header value |
|
|
210
|
+
| `--max-retries <n>` | Retries for transient `429`/`503` responses (default `2`) |
|
|
211
|
+
| `--max-response-bytes <n>` | Cap response body size in bytes (`0` = unlimited; default 100 MiB) |
|
|
171
212
|
|
|
172
|
-
|
|
173
|
-
npm test # builds, then runs `node --test` over dist/test
|
|
174
|
-
```
|
|
213
|
+
## Learn more
|
|
175
214
|
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
- **`cli.test.ts`** — end-to-end command parsing, validation and exit codes — mocked client.
|
|
215
|
+
- **[SKILLS.md](SKILLS.md)** — Claude Code Agent Skills that drive this CLI for live water-level questions.
|
|
216
|
+
- **[Usage.md](Usage.md)** — full use-case-driven cookbook.
|
|
217
|
+
- **[GLOSSARY.md](GLOSSARY.md)** — every domain term, timeseries code, and state classification explained.
|
|
218
|
+
- **[DEVELOPING.md](DEVELOPING.md)** — TypeScript library usage, architecture, testing, CI.
|
|
181
219
|
|
|
182
|
-
##
|
|
220
|
+
## Data license
|
|
183
221
|
|
|
184
|
-
|
|
222
|
+
This CLI is a **client** — it accesses data it does not own or redistribute. The
|
|
223
|
+
upstream data is © its provider and licensed **separately from this tool's code**.
|
|
224
|
+
See **[DATA_LICENSE.md](DATA_LICENSE.md)**.
|
|
185
225
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
- **docs.yml** — build TypeDoc API docs and deploy to GitHub Pages on each `v*` tag.
|
|
226
|
+
> **Wasserstraßen- und Schifffahrtsverwaltung des Bundes (WSV)** — Datenlizenz
|
|
227
|
+
> Deutschland **Zero** 2.0 (≈ CC0): no attribution required, commercial use and
|
|
228
|
+
> modification allowed. Raw, unchecked data — no warranty.
|
|
190
229
|
|
|
191
230
|
## License
|
|
192
231
|
|
package/dist/src/cli/program.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { defaultIO } from "./io.js";
|
|
8
8
|
import { PegelOnlineClient } from "../client/client.js";
|
|
9
|
-
import { parseIntArg } from "./shared.js";
|
|
9
|
+
import { parseIntArg, parseBaseUrl } from "./shared.js";
|
|
10
10
|
import { registerStationCommands } from "./commands/stations.js";
|
|
11
11
|
import { registerTimeseriesCommands } from "./commands/timeseries.js";
|
|
12
12
|
/**
|
|
@@ -38,7 +38,7 @@ export function buildProgram(deps = defaultDeps) {
|
|
|
38
38
|
.description("CLI for the open PEGELONLINE water-level REST API " +
|
|
39
39
|
"(https://www.pegelonline.wsv.de/webservices/rest-api/v2)")
|
|
40
40
|
.version(VERSION)
|
|
41
|
-
.option("--base-url <url>", "API base URL", "https://www.pegelonline.wsv.de")
|
|
41
|
+
.option("--base-url <url>", "API base URL", parseBaseUrl, "https://www.pegelonline.wsv.de")
|
|
42
42
|
.option("--timeout <ms>", "per-request timeout in milliseconds", parseIntArg)
|
|
43
43
|
.option("--user-agent <ua>", "User-Agent header value")
|
|
44
44
|
.option("--max-retries <n>", "retries for transient 429/503 responses", parseIntArg)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.js","sourceRoot":"","sources":["../../../src/cli/program.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,4EAA4E;AAC5E,mBAAmB;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../../../src/cli/program.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,4EAA4E;AAC5E,mBAAmB;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;;;;GAKG;AACH,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC5F,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;AAErC,yEAAyE;AACzE,MAAM,CAAC,MAAM,WAAW,GAAY;IAClC,EAAE,EAAE,SAAS;IACb,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,iBAAiB,CAAC,OAAO,CAAC;CAC1D,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,OAAgB,WAAW;IACtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,OAAO,CAAC;SACb,WAAW,CACV,oDAAoD;QAClD,0DAA0D,CAC7D;SACA,OAAO,CAAC,OAAO,CAAC;SAChB,MAAM,CAAC,kBAAkB,EAAE,cAAc,EAAE,YAAY,EAAE,gCAAgC,CAAC;SAC1F,MAAM,CAAC,gBAAgB,EAAE,qCAAqC,EAAE,WAAW,CAAC;SAC5E,MAAM,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;SACtD,MAAM,CAAC,mBAAmB,EAAE,yCAAyC,EAAE,WAAW,CAAC;SACnF,MAAM,CACL,0BAA0B,EAC1B,kEAAkE,EAClE,WAAW,CACZ;SACA,MAAM,CAAC,WAAW,EAAE,uDAAuD,CAAC;SAC5E,kBAAkB,EAAE,CAAC;IAExB,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvC,0BAA0B,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE1C,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/src/cli/shared.d.ts
CHANGED
|
@@ -2,6 +2,15 @@ import type { CliDeps } from "./io.js";
|
|
|
2
2
|
import type { EngineOptions } from "../client/engine.js";
|
|
3
3
|
/** commander value-parser: a non-negative integer. */
|
|
4
4
|
export declare function parseIntArg(value: string): number;
|
|
5
|
+
/**
|
|
6
|
+
* commander value-parser for `--base-url`: reject anything that is not a parseable
|
|
7
|
+
* absolute `http:`/`https:` URL at *parse* time, so a bad scheme (`file:`, `ftp:`)
|
|
8
|
+
* or malformed URL exits 2 (usage) — consistent with the blueprint — instead of
|
|
9
|
+
* surfacing later as a runtime PegelNetworkError (exit 1). The transport still
|
|
10
|
+
* enforces the same allowlist as the authoritative egress control; this only moves
|
|
11
|
+
* the user-facing rejection earlier and to the correct exit code.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseBaseUrl(value: string): string;
|
|
5
14
|
/**
|
|
6
15
|
* Validate a required positional argument: reject an empty/blank value rather
|
|
7
16
|
* than forwarding it into the URL path (which would produce a malformed request
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/cli/shared.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAU1E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,SAAM,GAAG,MAAM,CAE9E;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAQpE;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAGrF;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;IAC5C,MAAM,EAAE,aAAa,CAAC;IACtB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CACpB,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAQvC"}
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/cli/shared.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWlD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAU1E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,SAAM,GAAG,MAAM,CAE9E;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAQpE;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAGrF;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;IAC5C,MAAM,EAAE,aAAa,CAAC;IACtB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CACpB,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAQvC"}
|
package/dist/src/cli/shared.js
CHANGED
|
@@ -17,6 +17,27 @@ export function parseIntArg(value) {
|
|
|
17
17
|
}
|
|
18
18
|
return n;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* commander value-parser for `--base-url`: reject anything that is not a parseable
|
|
22
|
+
* absolute `http:`/`https:` URL at *parse* time, so a bad scheme (`file:`, `ftp:`)
|
|
23
|
+
* or malformed URL exits 2 (usage) — consistent with the blueprint — instead of
|
|
24
|
+
* surfacing later as a runtime PegelNetworkError (exit 1). The transport still
|
|
25
|
+
* enforces the same allowlist as the authoritative egress control; this only moves
|
|
26
|
+
* the user-facing rejection earlier and to the correct exit code.
|
|
27
|
+
*/
|
|
28
|
+
export function parseBaseUrl(value) {
|
|
29
|
+
let url;
|
|
30
|
+
try {
|
|
31
|
+
url = new URL(value);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new InvalidArgumentError("Expected an absolute http(s) URL.");
|
|
35
|
+
}
|
|
36
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
37
|
+
throw new InvalidArgumentError("Only http and https URLs are supported.");
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
20
41
|
/**
|
|
21
42
|
* Validate a required positional argument: reject an empty/blank value rather
|
|
22
43
|
* than forwarding it into the URL path (which would produce a malformed request
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/cli/shared.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/cli/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,14 @@ export interface EngineOptions {
|
|
|
13
13
|
transport?: Transport;
|
|
14
14
|
/** Value of the User-Agent header. */
|
|
15
15
|
userAgent?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Extra headers sent on every request. Credential-bearing headers
|
|
18
|
+
* (Authorization, Cookie, X-API-Key, Proxy-Authorization) are automatically
|
|
19
|
+
* stripped when a redirect crosses to a different origin, so they never leak to
|
|
20
|
+
* an arbitrary host named in Location. This client is keyless and sets none, but
|
|
21
|
+
* library consumers may add one.
|
|
22
|
+
*/
|
|
23
|
+
headers?: Record<string, string>;
|
|
16
24
|
/** Per-request timeout in milliseconds (0 disables). */
|
|
17
25
|
timeoutMs?: number;
|
|
18
26
|
/** Number of automatic retries for transient (429/503) responses. */
|
|
@@ -33,6 +41,7 @@ export declare class RequestEngine {
|
|
|
33
41
|
private readonly baseUrl;
|
|
34
42
|
private readonly transport;
|
|
35
43
|
private readonly userAgent;
|
|
44
|
+
private readonly extraHeaders;
|
|
36
45
|
private readonly timeoutMs;
|
|
37
46
|
private readonly maxRetries;
|
|
38
47
|
private readonly retryDelayMs;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/client/engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAGhE,eAAO,MAAM,gBAAgB,mCAAmC,CAAC;AAGjE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/client/engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAGhE,eAAO,MAAM,gBAAgB,mCAAmC,CAAC;AAGjE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAmDD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;gBAE1C,OAAO,GAAE,aAAkB;IAmBvC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM;IAMnD,6EAA6E;IACvE,OAAO,CACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAmC,GAChF,OAAO,CAAC,WAAW,CAAC;IAwDvB,0DAA0D;IACpD,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAU/D,OAAO,CAAC,UAAU;CAenB"}
|
|
@@ -7,11 +7,55 @@ import { PegelApiError, PegelError, PegelParseError } from "./errors.js";
|
|
|
7
7
|
export const DEFAULT_BASE_URL = "https://www.pegelonline.wsv.de";
|
|
8
8
|
const DEFAULT_USER_AGENT = "pegel-online-cli";
|
|
9
9
|
const DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024 * 1024;
|
|
10
|
+
/**
|
|
11
|
+
* Credential-bearing headers that must never be carried across an origin boundary
|
|
12
|
+
* on a redirect. Stored lower-cased and compared case-insensitively so a header
|
|
13
|
+
* added as `X-Api-Key` or `Authorization` is caught regardless of casing.
|
|
14
|
+
*/
|
|
15
|
+
const CREDENTIAL_HEADERS = new Set([
|
|
16
|
+
"authorization",
|
|
17
|
+
"cookie",
|
|
18
|
+
"x-api-key",
|
|
19
|
+
"proxy-authorization",
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Delete every credential-bearing header from `headers`, matching case-insensitively.
|
|
23
|
+
* The CLI sends none today (keyless), but RequestEngine is exported as a library and
|
|
24
|
+
* a keyed sibling/consumer could add one via a future headers option — keep the
|
|
25
|
+
* guarantee correct regardless of the casing the caller used.
|
|
26
|
+
*/
|
|
27
|
+
function stripSensitiveHeaders(headers) {
|
|
28
|
+
for (const key of Object.keys(headers)) {
|
|
29
|
+
if (CREDENTIAL_HEADERS.has(key.toLowerCase()))
|
|
30
|
+
delete headers[key];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Strip control characters (all C0/C1 controls except tab and newline, plus DEL)
|
|
35
|
+
* out of a string that originates in an attacker-controlled response — the error
|
|
36
|
+
* `detail`. `JSON.parse` decodes an escaped ESC in an error body into a real ESC
|
|
37
|
+
* byte, so without this a hostile or MITM'd endpoint
|
|
38
|
+
* could drive ANSI/OSC escape sequences into the user's terminal once the message
|
|
39
|
+
* is printed raw to stderr (title spoofing, output overwrite, OSC 52 clipboard).
|
|
40
|
+
* The success path is already safe (`JSON.stringify` escapes these), so this only
|
|
41
|
+
* needs to cover text that flows into an error message.
|
|
42
|
+
*/
|
|
43
|
+
function sanitizeServerText(text) {
|
|
44
|
+
let out = "";
|
|
45
|
+
for (const ch of text) {
|
|
46
|
+
const n = ch.codePointAt(0) ?? 0;
|
|
47
|
+
if (n <= 8 || (n >= 0x0b && n <= 0x1f) || (n >= 0x7f && n <= 0x9f))
|
|
48
|
+
continue;
|
|
49
|
+
out += ch;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
10
53
|
const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
54
|
export class RequestEngine {
|
|
12
55
|
baseUrl;
|
|
13
56
|
transport;
|
|
14
57
|
userAgent;
|
|
58
|
+
extraHeaders;
|
|
15
59
|
timeoutMs;
|
|
16
60
|
maxRetries;
|
|
17
61
|
retryDelayMs;
|
|
@@ -28,6 +72,7 @@ export class RequestEngine {
|
|
|
28
72
|
if (/[\x00-\x1f\x7f]/.test(this.userAgent)) {
|
|
29
73
|
throw new PegelError("Invalid User-Agent: control characters are not allowed.");
|
|
30
74
|
}
|
|
75
|
+
this.extraHeaders = options.headers ?? {};
|
|
31
76
|
this.timeoutMs = options.timeoutMs ?? 30_000;
|
|
32
77
|
this.maxRetries = options.maxRetries ?? 2;
|
|
33
78
|
this.retryDelayMs = options.retryDelayMs ?? 200;
|
|
@@ -45,6 +90,7 @@ export class RequestEngine {
|
|
|
45
90
|
async request(method, path, options = { accept: "application/json" }) {
|
|
46
91
|
let url = this.buildUrl(path, options.query);
|
|
47
92
|
const headers = {
|
|
93
|
+
...this.extraHeaders,
|
|
48
94
|
Accept: options.accept,
|
|
49
95
|
"User-Agent": this.userAgent,
|
|
50
96
|
};
|
|
@@ -71,12 +117,13 @@ export class RequestEngine {
|
|
|
71
117
|
const location = response.headers["location"];
|
|
72
118
|
if (typeof location === "string" && location.length > 0) {
|
|
73
119
|
const next = new URL(location, url);
|
|
74
|
-
// Security: never carry credential-bearing headers across
|
|
75
|
-
// CLI sends none today, but this guards a future
|
|
76
|
-
// header from leaking to an
|
|
120
|
+
// Security: never carry credential-bearing headers across an origin
|
|
121
|
+
// boundary. The CLI sends none today, but this guards a future
|
|
122
|
+
// Authorization/Cookie/X-Api-Key header from leaking to an
|
|
123
|
+
// attacker-controlled redirect target. Comparing full origin (scheme +
|
|
124
|
+
// host + port) also strips on a same-host https->http downgrade.
|
|
77
125
|
if (next.origin !== new URL(url).origin) {
|
|
78
|
-
|
|
79
|
-
delete headers["Cookie"];
|
|
126
|
+
stripSensitiveHeaders(headers);
|
|
80
127
|
}
|
|
81
128
|
url = next.toString();
|
|
82
129
|
redirects += 1;
|
|
@@ -114,6 +161,10 @@ export class RequestEngine {
|
|
|
114
161
|
catch {
|
|
115
162
|
// Non-JSON error body; leave detail undefined.
|
|
116
163
|
}
|
|
164
|
+
// `detail` came from the response body; strip control characters so a hostile
|
|
165
|
+
// endpoint cannot inject terminal escape sequences via the stderr error message.
|
|
166
|
+
if (detail !== undefined)
|
|
167
|
+
detail = sanitizeServerText(detail);
|
|
117
168
|
return new PegelApiError({ status, url, method, body: text, detail });
|
|
118
169
|
}
|
|
119
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/client/engine.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yEAAyE;AACzE,qCAAqC;AAErC,OAAO,EAAE,iBAAiB,EAAkB,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAoB,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,gCAAgC,CAAC;AACjE,MAAM,kBAAkB,GAAG,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/client/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,10 +17,5 @@ export interface HttpResponse {
|
|
|
17
17
|
body: Buffer;
|
|
18
18
|
}
|
|
19
19
|
export type Transport = (request: HttpRequest) => Promise<HttpResponse>;
|
|
20
|
-
/**
|
|
21
|
-
* Default transport. Resolves with the raw response (including non-2xx) — status
|
|
22
|
-
* interpretation is the client's job. Rejects only on transport-level failures
|
|
23
|
-
* (connection errors, timeouts, malformed URLs).
|
|
24
|
-
*/
|
|
25
20
|
export declare const nodeHttpTransport: Transport;
|
|
26
21
|
//# sourceMappingURL=http.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"AAQA,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"AAQA,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAgBxE,eAAO,MAAM,iBAAiB,EAAE,SAmG5B,CAAC"}
|
package/dist/src/client/http.js
CHANGED
|
@@ -13,6 +13,14 @@ import { PegelNetworkError } from "./errors.js";
|
|
|
13
13
|
* interpretation is the client's job. Rejects only on transport-level failures
|
|
14
14
|
* (connection errors, timeouts, malformed URLs).
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* Multiplier applied to the per-request idle timeout to derive an overall
|
|
18
|
+
* wall-clock deadline. `req.setTimeout` only fires on an *idle* socket — every
|
|
19
|
+
* received byte resets it — so a hostile server can trickle one byte just under
|
|
20
|
+
* the idle window forever and the request never times out. This bounds the total
|
|
21
|
+
* time a single request may take before it is destroyed, regardless of trickle.
|
|
22
|
+
*/
|
|
23
|
+
const OVERALL_DEADLINE_FACTOR = 10;
|
|
16
24
|
export const nodeHttpTransport = (request) => new Promise((resolve, reject) => {
|
|
17
25
|
let url;
|
|
18
26
|
try {
|
|
@@ -32,6 +40,23 @@ export const nodeHttpTransport = (request) => new Promise((resolve, reject) => {
|
|
|
32
40
|
const isHttps = url.protocol === "https:";
|
|
33
41
|
const driver = isHttps ? https : http;
|
|
34
42
|
const maxBytes = request.maxResponseBytes;
|
|
43
|
+
// Overall wall-clock deadline (see OVERALL_DEADLINE_FACTOR). Cleared on the
|
|
44
|
+
// first settle so it never leaks or keeps the event loop alive.
|
|
45
|
+
let deadlineTimer;
|
|
46
|
+
const clearDeadline = () => {
|
|
47
|
+
if (deadlineTimer !== undefined) {
|
|
48
|
+
clearTimeout(deadlineTimer);
|
|
49
|
+
deadlineTimer = undefined;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const settleResolve = (value) => {
|
|
53
|
+
clearDeadline();
|
|
54
|
+
resolve(value);
|
|
55
|
+
};
|
|
56
|
+
const settleReject = (err) => {
|
|
57
|
+
clearDeadline();
|
|
58
|
+
reject(err);
|
|
59
|
+
};
|
|
35
60
|
const req = driver.request(url, {
|
|
36
61
|
method: request.method,
|
|
37
62
|
headers: request.headers,
|
|
@@ -46,7 +71,7 @@ export const nodeHttpTransport = (request) => new Promise((resolve, reject) => {
|
|
|
46
71
|
if (maxBytes !== undefined && received > maxBytes) {
|
|
47
72
|
aborted = true;
|
|
48
73
|
res.destroy();
|
|
49
|
-
|
|
74
|
+
settleReject(new PegelNetworkError(`Response exceeded maxResponseBytes (${maxBytes})`));
|
|
50
75
|
return;
|
|
51
76
|
}
|
|
52
77
|
chunks.push(chunk);
|
|
@@ -54,7 +79,7 @@ export const nodeHttpTransport = (request) => new Promise((resolve, reject) => {
|
|
|
54
79
|
res.on("end", () => {
|
|
55
80
|
if (aborted)
|
|
56
81
|
return;
|
|
57
|
-
|
|
82
|
+
settleResolve({
|
|
58
83
|
status: res.statusCode ?? 0,
|
|
59
84
|
headers: res.headers,
|
|
60
85
|
body: Buffer.concat(chunks),
|
|
@@ -63,17 +88,26 @@ export const nodeHttpTransport = (request) => new Promise((resolve, reject) => {
|
|
|
63
88
|
res.on("error", (err) => {
|
|
64
89
|
if (aborted)
|
|
65
90
|
return; // we already rejected with the size-cap error
|
|
66
|
-
|
|
91
|
+
settleReject(new PegelNetworkError(`Response stream error: ${err.message}`, { cause: err }));
|
|
67
92
|
});
|
|
68
93
|
});
|
|
69
94
|
if (request.timeoutMs && request.timeoutMs > 0) {
|
|
95
|
+
// Idle-socket timeout: fires when no bytes move for timeoutMs.
|
|
70
96
|
req.setTimeout(request.timeoutMs, () => {
|
|
71
97
|
req.destroy(new PegelNetworkError(`Request timed out after ${request.timeoutMs}ms`));
|
|
72
98
|
});
|
|
99
|
+
// Overall deadline: a hostile server can trickle bytes just under the idle
|
|
100
|
+
// window forever, so cap total wall-clock time as well.
|
|
101
|
+
const overallMs = request.timeoutMs * OVERALL_DEADLINE_FACTOR;
|
|
102
|
+
deadlineTimer = setTimeout(() => {
|
|
103
|
+
req.destroy(new PegelNetworkError(`Request exceeded overall deadline of ${overallMs}ms`));
|
|
104
|
+
}, overallMs);
|
|
105
|
+
// Do not let the deadline timer alone keep the process alive.
|
|
106
|
+
deadlineTimer.unref?.();
|
|
73
107
|
}
|
|
74
108
|
req.on("error", (err) => {
|
|
75
109
|
// A timeout destroy already passes an PegelNetworkError; don't double-wrap.
|
|
76
|
-
|
|
110
|
+
settleReject(err instanceof PegelNetworkError ? err : new PegelNetworkError(err.message, { cause: err }));
|
|
77
111
|
});
|
|
78
112
|
if (request.body !== undefined)
|
|
79
113
|
req.write(request.body);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,iDAAiD;AACjD,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,8EAA8E;AAC9E,yCAAyC;AAEzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAuBhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAc,CAAC,OAAO,EAAE,EAAE,CACtD,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC5C,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,iBAAiB,CAAC,gBAAgB,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,2DAA2D;IAC3D,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,CAAC,IAAI,iBAAiB,CAAC,yBAAyB,GAAG,CAAC,QAAQ,aAAa,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/F,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAE1C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CACxB,GAAG,EACH;QACE,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,EACD,CAAC,GAAG,EAAE,EAAE;QACN,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAC/B,IAAI,OAAO;gBAAE,OAAO;YACpB,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;YACzB,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC;gBAClD,OAAO,GAAG,IAAI,CAAC;gBACf,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED