@dutodev/cli 0.6.0 → 1.0.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/README.md +121 -99
- package/dist/{chunk-26EQC6HB.js → chunk-BPOXISIM.js} +38 -2
- package/dist/{fullscreen-7P5GBCQX.js → fullscreen-IWZCH3IP.js} +69 -76
- package/dist/index.js +1094 -198
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
# duto
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Put a local service on the internet at a stable HTTPS URL, with one command:
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
duto 3000 --name web # https://web-<
|
|
6
|
+
duto 3000 --name web # https://web-<user>.duto.dev → localhost:3000
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The URL stays the same from one session to the next. That makes it good for webhooks, login callbacks, testing on your phone, or showing someone your work without deploying.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Duto is invite-only for now. You create your account at [duto.dev](https://duto.dev) from an invite link.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Install
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
npm i -g @dutodev/cli
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Requires Node 18 or later. On first run, the CLI downloads a pinned release of the official Cloudflare `cloudflared`, checks its SHA-256 and keeps it in `~/.duto/bin`. Nothing else to install.
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Get started
|
|
22
22
|
|
|
23
|
-
1.
|
|
24
|
-
2.
|
|
23
|
+
1. At [duto.dev](https://duto.dev), open **Authtoken** and generate your token.
|
|
24
|
+
2. Connect the CLI to your account (once per machine):
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
duto login <token>
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
3.
|
|
30
|
+
3. Open a tunnel:
|
|
31
31
|
|
|
32
32
|
```
|
|
33
|
-
duto 3000 # https://<
|
|
34
|
-
duto 3000 --name web # https://web-<
|
|
33
|
+
duto 3000 # https://<user>.duto.dev
|
|
34
|
+
duto 3000 --name web # https://web-<user>.duto.dev
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Every name you use with `--name` is reserved for you and keeps the same URL. Names take lowercase letters and numbers only, no hyphens.
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Several tunnels: `duto.yml`
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Create a `duto.yml` (or `duto.yaml`) at the root of your project and just run `duto`:
|
|
42
42
|
|
|
43
43
|
```yaml
|
|
44
44
|
tunnels:
|
|
@@ -47,137 +47,159 @@ tunnels:
|
|
|
47
47
|
webhook: 4000
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
All tunnels start together, in one screen. The CLI looks for the file in the current folder and its parents, up to the git root. If you commit it, everyone on the team gets their own addresses (`web-<user>.duto.dev`) from the same file.
|
|
51
51
|
|
|
52
|
-
### Host
|
|
52
|
+
### Host and headers
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
By default, your local service gets the request as it arrived from the internet: `Host: web-<user>.duto.dev`, `X-Forwarded-Proto: https` and the Cloudflare headers (`CF-Connecting-IP` and others). That is enough for most apps. When your app decides things by domain, you have two tools per tunnel:
|
|
55
55
|
|
|
56
56
|
```yaml
|
|
57
57
|
tunnels:
|
|
58
|
-
|
|
58
|
+
shop:
|
|
59
59
|
port: 8080
|
|
60
|
-
headers: #
|
|
61
|
-
X-Forwarded-Host:
|
|
62
|
-
X-Debug: "" #
|
|
63
|
-
|
|
60
|
+
headers: # add or override headers
|
|
61
|
+
X-Forwarded-Host: shop.myapp.test
|
|
62
|
+
X-Debug: "" # an empty value removes the header
|
|
63
|
+
admin:
|
|
64
64
|
port: 8081
|
|
65
|
-
host:
|
|
65
|
+
host: admin.myapp.test # replace the Host your service receives
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
**
|
|
68
|
+
**Which one to use:**
|
|
69
69
|
|
|
70
|
-
|
|
|
70
|
+
| Situation | Use | Why |
|
|
71
71
|
|---|---|---|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
72
|
+
| A local reverse proxy (Traefik, Nginx, Caddy) picks the service by Host | `host:` | The proxy only knows your local domains |
|
|
73
|
+
| Several tunnels on one port, one per domain | a different `host:` or `headers:` on each | A port can repeat; each tunnel has its own address |
|
|
74
|
+
| The app identifies the domain or customer by `X-Forwarded-Host` but builds URLs from `Host` | `headers:` with `X-Forwarded-Host` | The app sees the local domain and keeps generating public links |
|
|
75
|
+
| OAuth login where the app computes `redirect_uri` itself | avoid `host:`; try `headers:` | See below |
|
|
76
76
|
|
|
77
|
-
**
|
|
77
|
+
**What `host:` does:**
|
|
78
78
|
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
- Links
|
|
82
|
-
-
|
|
79
|
+
- Your service receives the Host you set, and `X-Forwarded-Host` is not sent. To send it anyway, put it in `headers:`.
|
|
80
|
+
- Redirects come back with the public address. A `Location: http://admin.myapp.test:8081/login` reaches the browser as `https://admin-<user>.duto.dev/login`. This also applies inside encoded parameters of the `Location`. To turn it off, set `rewrite_redirects: false` on the tunnel.
|
|
81
|
+
- Links in response bodies (HTML, JSON) are not rewritten. URLs your app builds from the Host only open on your machine.
|
|
82
|
+
- On the command line: `duto 8081 --host admin.myapp.test`, and `--no-rewrite-redirects` to turn the swap off.
|
|
83
83
|
|
|
84
|
-
**OAuth
|
|
84
|
+
**OAuth and `host:`.** In the authorization code flow, the app sends a `redirect_uri` twice: once when the browser goes to the login provider, and again, server to server, when it exchanges the code for a token. The provider requires both to be identical. With `host:`, the app builds both from the local domain. Duto fixes the first, which goes through the browser, but cannot reach the second, which never passes through the tunnel. The provider rejects the exchange, usually with `invalid_request` or `invalid_grant`, and the app often loops back to login (`ERR_TOO_MANY_REDIRECTS`).
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
Ways out, from simplest to most work:
|
|
87
87
|
|
|
88
|
-
1.
|
|
89
|
-
2.
|
|
90
|
-
3.
|
|
88
|
+
1. Do not swap the Host. The app builds `redirect_uri` from the public address both times.
|
|
89
|
+
2. If the app needs the local domain for other decisions and reads `X-Forwarded-Host`, pass the domain through `headers:` and keep the public Host.
|
|
90
|
+
3. Set the public callback URL in the app, if it lets you.
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
In every case, register `https://<name>-<user>.duto.dev/<callback path>` as the callback URL at the provider. The comparison is usually exact, with no wildcards: each person on the team has their own address.
|
|
93
93
|
|
|
94
|
-
**
|
|
94
|
+
**About `X-Forwarded-*`.** `X-Forwarded-Host`, `X-Forwarded-Proto` and `X-Forwarded-For` are an old convention between reverse proxies for telling the app what the client actually asked for. The standard version is the `Forwarded` header (RFC 7239), which is less used in practice. Frameworks usually read these headers only when the proxy is marked as trusted (`trusted proxies`, `trust proxy`, `USE_X_FORWARDED_HOST`), so what `headers:` does depends on how your app is configured. On the other hand, if an app reads `X-Forwarded-Host` without that check, any client can forge it in production. Make sure the proxy in front overwrites or removes that header.
|
|
95
95
|
|
|
96
|
-
##
|
|
96
|
+
## Password-protect a tunnel
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
Without a password, anyone with the URL can reach your development environment. To protect a tunnel, add `auth` (and `allow`, if you need it) to `duto.yml`:
|
|
99
99
|
|
|
100
100
|
```yaml
|
|
101
101
|
tunnels:
|
|
102
102
|
backend:
|
|
103
103
|
port: 3000
|
|
104
104
|
auth:
|
|
105
|
-
-
|
|
106
|
-
- ana:${
|
|
107
|
-
|
|
105
|
+
- client:${CLIENT_PASSWORD}
|
|
106
|
+
- ana:${ANA_PASSWORD}
|
|
107
|
+
allow: [stripe, github, 203.0.113.0/24]
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
- `auth`
|
|
111
|
-
- `${VAR}`
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
110
|
+
- `auth` is a list of `user:password`. Use one pair per person. The first `:` separates the user from the password; the password is 8 to 128 characters.
|
|
111
|
+
- `${VAR}` reads an environment variable. Always use it: that keeps the password out of git along with `duto.yml`. If the variable does not exist, the CLI stops and prints the line number. The name can only have uppercase letters, digits and `_` (`${ANA_PASSWORD}`); anything else, like `${password}`, is left as text and becomes part of the password.
|
|
112
|
+
- In a one-line list in brackets, `${VAR}` needs quotes (`auth: ["client:${CLIENT_PASSWORD}"]`), because `{` is special in YAML. In a block list, as above, it does not.
|
|
113
|
+
- On the command line, for a single tunnel: `duto 3000 --auth client:my-password1 --allow stripe`. Both options can be repeated. They only work with a port; with a `duto.yml`, put them in the file.
|
|
114
|
+
- Careful: a password passed to `--auth` ends up in your shell history and is visible to anyone who runs `ps` on the machine. For anything beyond a quick test, use `duto.yml` with `${VAR}`.
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
People get in one of these ways:
|
|
117
117
|
|
|
118
|
-
|
|
|
118
|
+
| Who | How |
|
|
119
119
|
|---|---|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
| `curl`, Postman, scripts | `curl -u
|
|
123
|
-
| API
|
|
120
|
+
| Person in a browser | Opens the URL, sees the "This tunnel is password protected" page and signs in with user and password |
|
|
121
|
+
| Phone | Scans the QR code (key `m`, see below) and gets in without typing |
|
|
122
|
+
| `curl`, Postman, scripts | `curl -u client:password https://backend-<user>.duto.dev/` (`Authorization: Basic`) |
|
|
123
|
+
| An API that already uses `Authorization: Bearer` | Send `X-Duto-Auth: client:password`; the `Bearer` goes through to the app untouched |
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
Without credentials, a browser is sent to the sign-in page and other clients get `401`. Your local app never receives Duto's credentials: the `Authorization: Basic` that matched, `X-Duto-Auth` and the tunnel cookie are stripped before the request is forwarded (other cookies pass through). An `Authorization: Bearer` from your own app passes unchanged.
|
|
126
126
|
|
|
127
|
-
**
|
|
127
|
+
**Logging out and sessions.** In a browser, signing in sets a cookie that is valid for that tunnel only. To log out, open `/__duto/logout`: it clears the cookie and returns to the sign-in page. The cookie lasts until the browser closes or the CLI stops. Restarting the CLI signs everyone out, because the key that signs the cookies is new every session. The cookie only counts on requests to the tunnel itself: a page on another origin (another tunnel, another site) that calls the tunnel has to send `X-Duto-Auth` or `Authorization: Basic`. So opening the tunnel from a link on another `*.duto.dev` address (the dashboard or another tunnel) asks for the password again; a link from outside (email, chat) or a typed URL gets straight in.
|
|
128
128
|
|
|
129
|
-
**QR code.**
|
|
129
|
+
**QR code.** With the full screen open, key `m` shows the QR of the selected tunnel in place of the request list. On a tunnel without a password, the QR points to the tunnel URL. On a tunnel with a password, it points to a link that signs in directly, as the first user in the `auth` list. That link is valid for 5 minutes and for one device only: once opened, or after it expires, it falls back to the sign-in page. On the QR screen, `g` generates a new link (and invalidates the previous one) and `esc` goes back to the list.
|
|
130
130
|
|
|
131
|
-
**CORS.**
|
|
131
|
+
**CORS.** A frontend on another domain keeps working. The preflight (`OPTIONS` with `Origin` and `Access-Control-Request-Method`, no body) goes through without a password, but only the status and the CORS headers of your app's response come back from it. When the app answers with CORS, Duto adds `X-Duto-Auth` to `Access-Control-Allow-Headers`. You change nothing in the app. The `401` response also carries CORS headers, so the frontend can read the error instead of seeing a network failure.
|
|
132
132
|
|
|
133
|
-
**Webhooks.** `
|
|
133
|
+
**Webhooks.** `allow` lets certain sources in without a password: the names `stripe` and `github` (the CLI downloads their official IP lists, keeps them for a day in `~/.duto/` and falls back to a built-in copy if the network fails) and IP ranges, IPv4 or IPv6, like `203.0.113.0/24`. `allow` only works together with `auth`.
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
On screen, password-protected tunnels show a lock (🔒) next to the URL. The request list shows who signed in and why `allow` let a request through; requests turned away with `401` are labeled "no password", and browsers turned away show up as a `303` redirect to the sign-in page. A tunnel opened with `n` in the full screen has no password.
|
|
136
136
|
|
|
137
|
-
##
|
|
137
|
+
## The screen
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
With your tunnels live, the terminal shows each tunnel (URL, local target, requests and errors) and the requests as they arrive: method, path, status, time and size. WebSocket works as usual and shows up as `WS 101`.
|
|
140
140
|
|
|
141
|
-
|
|
|
141
|
+
| Key | What it does |
|
|
142
142
|
|---|---|
|
|
143
|
-
| `n` |
|
|
144
|
-
| `c` / `o` |
|
|
145
|
-
| `m` |
|
|
146
|
-
| `f` |
|
|
147
|
-
| `↵` |
|
|
148
|
-
| `r` |
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
143
|
+
| `n` | Open another tunnel (this session only) |
|
|
144
|
+
| `c` / `o` | Copy / open in the browser the selected tunnel's URL |
|
|
145
|
+
| `m` | Show the QR code of the selected tunnel, to open on your phone |
|
|
146
|
+
| `f` | Filter by tunnel, method, path or status |
|
|
147
|
+
| `↵` | Request details: headers and the start of the bodies |
|
|
148
|
+
| `r` | In the details, replay the request against your local service |
|
|
149
|
+
| `i` | Open the inspector in the browser |
|
|
150
|
+
| `tab`, `↑↓` | Switch between tunnels and requests, and move the selection |
|
|
151
|
+
| `q` or Ctrl+C | Close the tunnels and quit |
|
|
151
152
|
|
|
152
|
-
**
|
|
153
|
+
**Replaying a request.** In the details (`↵`), key `r` sends the same method, path, headers and body your app received back to your local service, with the tunnel's `host:` and `headers:` rules applied and without Duto's own credentials. The replay joins the list marked with `↻`, the screen jumps to its details (`↻ replay of #N`) and `esc` goes back. It is handy for debugging webhooks without waiting for the external service to send again; in particular, a request that got `503` because your local service was down can be replayed as soon as it is up. Because the bytes are identical, webhook signatures stay valid. Apps that reject old events (Stripe tolerates 5 minutes by default) will reject a replay after that window.
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
You cannot replay WebSocket requests, requests blocked by the tunnel password, binary bodies, bodies over 64 KB, requests whose body had not finished arriving, or anything once the tunnel is no longer open; in those cases a line explains why. Replay is not available in the plain log (no interactive terminal).
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
If the tunnel is live but nothing answers on the local port, anyone opening the address in a browser sees a Duto page that reloads itself when the service comes up. It waits on a single connection (SSE): the CLI checks the port on your machine and tells the page as soon as the service responds, with no periodic reloading. Clients that are not browsers (`curl`, APIs) get the same response as text. Either way the status is `503` with `Retry-After: 3`. Paths under `/__duto/…` are reserved for this and never reach your app.
|
|
157
158
|
|
|
158
|
-
|
|
159
|
+
Outside an interactive terminal (pipe, CI) or with `--no-ui`, the CLI prints a plain log. On Windows the full screen is off by default; use `--ui` to try it.
|
|
159
160
|
|
|
160
|
-
##
|
|
161
|
+
## Inspector in the browser
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
With every session, the CLI starts a local inspector: a browser page with requests arriving live, formatted JSON, and buttons to copy and replay. It is more comfortable than the terminal for large bodies.
|
|
164
|
+
|
|
165
|
+
- **How to open it.** In the full screen, key `i` opens the browser at the right address, and the screen shows `inspector http://127.0.0.1:<port>`. In plain mode (pipe, CI, `--no-ui`), the CLI prints `inspector: <url>?t=<token>`; copy that whole address.
|
|
166
|
+
- **Port.** The default is `127.0.0.1:4141`. If it is taken, the CLI tries the next ones up to 4161 and then any free port. The inspector listens on `127.0.0.1` only: other machines on your network cannot reach it.
|
|
167
|
+
- **Token.** The address carries a session token (`?t=...`). It exists so other sites open in your browser cannot read or replay your requests. That is why the full screen does not show the token; use key `i`.
|
|
168
|
+
- **Turn it off.** `--no-inspect` does not start the inspector. In plain mode the address with the token goes to the output; if that goes to a shared log, use `--no-inspect`.
|
|
169
|
+
- **Nothing goes to disk.** Requests stay in the session's memory and disappear when it ends. It works offline.
|
|
170
|
+
|
|
171
|
+
The page has the list on the left and the details on the right, with the tabs **Body**, **Headers**, **Response** and **Raw**. JSON is re-indented exactly as it arrived, with syntax errors highlighted.
|
|
172
|
+
|
|
173
|
+
| Action | How |
|
|
172
174
|
|---|---|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
| `--no-ui` / `--ui` | Desliga / força a tela cheia |
|
|
175
|
+
| Copy the body | **Copy body** copies the body of the active tab |
|
|
176
|
+
| Copy as `curl` | **Copy as curl**; on a tunnel with a password, the command carries `-H 'X-Duto-Auth: <user>:PASSWORD'` and you swap `PASSWORD` for the password |
|
|
177
|
+
| Replay | **↻ Replay** or key `r`, with the same rules as the terminal |
|
|
178
|
+
| Filter | `/` focuses the filter |
|
|
179
|
+
| Navigate | `↑` and `↓` move the selection |
|
|
179
180
|
|
|
180
|
-
##
|
|
181
|
+
## Commands
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
| Command | What it does |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `duto` | Open the tunnels in `duto.yml` |
|
|
186
|
+
| `duto <port>` | Open a tunnel to `localhost:<port>` |
|
|
187
|
+
| `duto http <port>` | Same, for people coming from ngrok |
|
|
188
|
+
| `duto login <token>` | Save the authtoken on this machine |
|
|
189
|
+
| `duto status` | Show your account and live tunnels |
|
|
190
|
+
| `duto logout` | Remove the authtoken from this machine |
|
|
191
|
+
|
|
192
|
+
| Option | What it does |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `--name <name>` | Fixed address `<name>-<user>.duto.dev` |
|
|
195
|
+
| `--host <host>` | Host the local service receives instead of the public one |
|
|
196
|
+
| `--no-rewrite-redirects` | With `--host`, keep redirects as the service sent them |
|
|
197
|
+
| `--auth <user:password>` | Ask for a username and password to open the address (repeatable) |
|
|
198
|
+
| `--allow <range>` | With `--auth`: IP, CIDR range or name (`stripe`, `github`) that gets in without a password (repeatable) |
|
|
199
|
+
| `--no-ui` / `--ui` | Turn the full screen off / force it on |
|
|
200
|
+
| `--no-inspect` | Do not start the browser inspector (key `i` then shows "Inspector is off (--no-inspect).") |
|
|
201
|
+
|
|
202
|
+
## Configuration
|
|
203
|
+
|
|
204
|
+
- Config lives in `~/.duto/config.json` (mode 600). `DUTO_HOME` changes that folder.
|
|
205
|
+
- `DUTO_API_URL` points the CLI at another server (default: `https://duto.dev`).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ../shared/src/names.ts
|
|
4
|
+
var EMAIL_SENDING_SUBDOMAINS = ["send"];
|
|
4
5
|
var RESERVED_NAMES = [
|
|
5
6
|
"www",
|
|
6
7
|
"api",
|
|
@@ -23,8 +24,7 @@ var RESERVED_NAMES = [
|
|
|
23
24
|
"help",
|
|
24
25
|
"support",
|
|
25
26
|
"blog",
|
|
26
|
-
|
|
27
|
-
"send"
|
|
27
|
+
...EMAIL_SENDING_SUBDOMAINS
|
|
28
28
|
];
|
|
29
29
|
var MAX_NAME = 40;
|
|
30
30
|
var FORMAT = /^[a-z0-9]+$/;
|
|
@@ -38,6 +38,37 @@ function validateName(name, max = MAX_NAME) {
|
|
|
38
38
|
return { ok: true };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// src/ui/theme.ts
|
|
42
|
+
var C = {
|
|
43
|
+
signal: "#2dd4bf",
|
|
44
|
+
muted: "#8a8f8b",
|
|
45
|
+
strong: "#f3f4f1",
|
|
46
|
+
ok: "#4ade80",
|
|
47
|
+
warn: "#facc15",
|
|
48
|
+
err: "#f87171"
|
|
49
|
+
};
|
|
50
|
+
var TUNNEL_COLORS = ["#93c5fd", "#c4b5fd", "#facc15", "#4ade80", "#f9a8d4", "#fdba74", "#67e8f9"];
|
|
51
|
+
function tunnelColor(index) {
|
|
52
|
+
return TUNNEL_COLORS[index % TUNNEL_COLORS.length];
|
|
53
|
+
}
|
|
54
|
+
function fit(text, width) {
|
|
55
|
+
if (width <= 0) return "";
|
|
56
|
+
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/session/bodyNote.ts
|
|
60
|
+
var TRUNCATED_NOTE = "Body cut at 64 KB.";
|
|
61
|
+
function bodyNote(r, side) {
|
|
62
|
+
if (side === "req") {
|
|
63
|
+
if (r.reqBodyBytes === 0) return "No body.";
|
|
64
|
+
if (r.reqBody === null && !r.reqTruncated) return "Binary body; not stored.";
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (r.bytes === 0) return "No body.";
|
|
68
|
+
if (r.resBody === null) return "Binary body; not stored.";
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
41
72
|
// src/ui/format.ts
|
|
42
73
|
function statusColor(status) {
|
|
43
74
|
if (status === null) return "muted";
|
|
@@ -66,6 +97,11 @@ function localLabel(t) {
|
|
|
66
97
|
export {
|
|
67
98
|
normalizeName,
|
|
68
99
|
validateName,
|
|
100
|
+
C,
|
|
101
|
+
tunnelColor,
|
|
102
|
+
fit,
|
|
103
|
+
TRUNCATED_NOTE,
|
|
104
|
+
bodyNote,
|
|
69
105
|
statusColor,
|
|
70
106
|
formatDuration,
|
|
71
107
|
formatBytes,
|