@20syldev/api 5.6.0 → 5.7.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/.prettierignore +5 -0
- package/README.md +204 -80
- package/dist/app.d.ts +2 -0
- package/dist/config/env.d.ts +21 -0
- package/dist/config/env.js +10 -2
- package/dist/config/env.js.map +1 -1
- package/dist/config/plans.d.ts +26 -0
- package/dist/config/versions.d.ts +18 -0
- package/dist/constants.d.ts +39 -0
- package/dist/constants.js +0 -1
- package/dist/constants.js.map +1 -1
- package/dist/middleware/cors.d.ts +2 -0
- package/dist/middleware/cors.js +2 -1
- package/dist/middleware/cors.js.map +1 -1
- package/dist/middleware/error.d.ts +4 -0
- package/dist/middleware/json.d.ts +2 -0
- package/dist/middleware/logger.d.ts +26 -0
- package/dist/middleware/logger.js +34 -1
- package/dist/middleware/logger.js.map +1 -1
- package/dist/middleware/ratelimit.d.ts +2 -0
- package/dist/middleware/version.d.ts +3 -0
- package/dist/middleware/version.js +2 -2
- package/dist/middleware/version.js.map +1 -1
- package/dist/modules/v3/algorithms.d.ts +82 -0
- package/dist/modules/v3/captcha.d.ts +8 -0
- package/dist/modules/v3/chat.d.ts +9 -0
- package/dist/modules/v3/chat.js +3 -3
- package/dist/modules/v3/chat.js.map +1 -1
- package/dist/modules/v3/color.d.ts +6 -0
- package/dist/modules/v3/convert.d.ts +10 -0
- package/dist/modules/v3/domain.d.ts +6 -0
- package/dist/modules/v3/hash.d.ts +8 -0
- package/dist/modules/v3/hyperplanning.d.ts +9 -0
- package/dist/modules/v3/levenshtein.d.ts +9 -0
- package/dist/modules/v3/personal.d.ts +6 -0
- package/dist/modules/v3/qrcode.d.ts +8 -0
- package/dist/modules/v3/tic_tac_toe.d.ts +9 -0
- package/dist/modules/v3/tic_tac_toe.js +3 -3
- package/dist/modules/v3/tic_tac_toe.js.map +1 -1
- package/dist/modules/v3/time.d.ts +12 -0
- package/dist/modules/v3/token.d.ts +9 -0
- package/dist/modules/v3/username.d.ts +6 -0
- package/dist/modules/v3/utils.d.ts +50 -0
- package/dist/modules/v3.d.ts +15 -0
- package/dist/modules/v4/address.d.ts +20 -0
- package/dist/modules/v4/agent.d.ts +30 -0
- package/dist/modules/v4/algorithms.d.ts +90 -0
- package/dist/modules/v4/avatar.d.ts +23 -0
- package/dist/modules/v4/barcode.d.ts +27 -0
- package/dist/modules/v4/captcha.d.ts +22 -0
- package/dist/modules/v4/chat.d.ts +21 -0
- package/dist/modules/v4/chat.js +3 -3
- package/dist/modules/v4/chat.js.map +1 -1
- package/dist/modules/v4/color.d.ts +16 -0
- package/dist/modules/v4/convert.d.ts +15 -0
- package/dist/modules/v4/credit.d.ts +22 -0
- package/dist/modules/v4/cron.d.ts +17 -0
- package/dist/modules/v4/dice.d.ts +16 -0
- package/dist/modules/v4/domain.d.ts +6 -0
- package/dist/modules/v4/encode.d.ts +81 -0
- package/dist/modules/v4/geo.d.ts +30 -0
- package/dist/modules/v4/hash.d.ts +15 -0
- package/dist/modules/v4/hyperplanning.d.ts +19 -0
- package/dist/modules/v4/ip.d.ts +19 -0
- package/dist/modules/v4/levenshtein.d.ts +13 -0
- package/dist/modules/v4/palette.d.ts +19 -0
- package/dist/modules/v4/password.d.ts +25 -0
- package/dist/modules/v4/personal.d.ts +6 -0
- package/dist/modules/v4/placeholder.d.ts +30 -0
- package/dist/modules/v4/qrcode.d.ts +25 -0
- package/dist/modules/v4/regex.d.ts +23 -0
- package/dist/modules/v4/statistics.d.ts +20 -0
- package/dist/modules/v4/text.d.ts +43 -0
- package/dist/modules/v4/tic_tac_toe.d.ts +19 -0
- package/dist/modules/v4/tic_tac_toe.js +3 -3
- package/dist/modules/v4/tic_tac_toe.js.map +1 -1
- package/dist/modules/v4/time.d.ts +15 -0
- package/dist/modules/v4/token.d.ts +9 -0
- package/dist/modules/v4/username.d.ts +6 -0
- package/dist/modules/v4/validate.d.ts +34 -0
- package/dist/modules/v4.d.ts +32 -0
- package/dist/modules/v5/address.d.ts +5 -0
- package/dist/modules/v5/algorithms.d.ts +9 -0
- package/dist/modules/v5/asymmetric.d.ts +26 -0
- package/dist/modules/v5/barcode.d.ts +27 -0
- package/dist/modules/v5/case.d.ts +10 -0
- package/dist/modules/v5/chart.d.ts +53 -0
- package/dist/modules/v5/chat.d.ts +21 -0
- package/dist/modules/v5/chat.js +3 -3
- package/dist/modules/v5/chat.js.map +1 -1
- package/dist/modules/v5/csv.d.ts +29 -0
- package/dist/modules/v5/evaluate.d.ts +14 -0
- package/dist/modules/v5/jwt.d.ts +11 -0
- package/dist/modules/v5/matrix.d.ts +77 -0
- package/dist/modules/v5/otp.d.ts +30 -0
- package/dist/modules/v5/symmetric.d.ts +15 -0
- package/dist/modules/v5/text.d.ts +5 -0
- package/dist/modules/v5/tic_tac_toe.d.ts +19 -0
- package/dist/modules/v5/tic_tac_toe.js +3 -3
- package/dist/modules/v5/tic_tac_toe.js.map +1 -1
- package/dist/modules/v5/url.d.ts +18 -0
- package/dist/modules/v5/url.js +12 -3
- package/dist/modules/v5/url.js.map +1 -1
- package/dist/modules/v5.d.ts +17 -0
- package/dist/routes/delete.d.ts +2 -0
- package/dist/routes/delete.js +2 -2
- package/dist/routes/delete.js.map +1 -1
- package/dist/routes/get.d.ts +2 -0
- package/dist/routes/get.js +84 -85
- package/dist/routes/get.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.js +8 -4
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/patch.d.ts +2 -0
- package/dist/routes/patch.js +1 -1
- package/dist/routes/patch.js.map +1 -1
- package/dist/routes/post.d.ts +2 -0
- package/dist/routes/post.js +32 -32
- package/dist/routes/post.js.map +1 -1
- package/dist/storage/index.d.ts +4 -0
- package/dist/types/storage.d.ts +49 -0
- package/dist/utils/colors.d.ts +44 -0
- package/dist/utils/helpers.d.ts +55 -0
- package/dist/utils/response.d.ts +9 -0
- package/dist/utils/response.js +2 -4
- package/dist/utils/response.js.map +1 -1
- package/package.json +17 -16
- package/robots.txt +74 -0
- package/src/favicon.ico +0 -0
package/.prettierignore
ADDED
package/README.md
CHANGED
|
@@ -1,50 +1,192 @@
|
|
|
1
|
-
|
|
2
|
-
<a href="https://api.sylvain.sh"><img src="https://api.sylvain.sh/favicon.ico" alt="Logo" width="25%" height="auto"/></a>
|
|
1
|
+
# @20syldev/api
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
## About
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
`@20syldev/api` bundles 42 self-contained utilities: encryption, matrix math, SVG charts, CSV, JWT, TOTP, barcodes, fake data generation, text processing, validation and more. Install it and import — no configuration, no setup step, types included.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
It works two ways:
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
- **As a library** — import any module directly into your code.
|
|
10
|
+
- **As a server** — a single import boots an Express app that exposes every module over HTTP.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
The HTTP surface is versioned from `v1` to `v5`, and every version stays mounted side by side, so a new release never changes the behaviour of the one you already target. The full endpoint reference lives on [docs.sylvain.sh](https://docs.sylvain.sh).
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
L'API est développée avec **TypeScript** et **Node.js**, et hébergée **24h/7j**. Elle est **simple d'utilisation** et a une **documentation** disponible sur [docs.sylvain.sh](https://docs.sylvain.sh) !
|
|
14
|
+
## Requirements
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
**Node.js >= 22.12.0.** The package ships as ESM, but that does not lock out CommonJS: Node has been able to `require()` an ESM module since 22.12, which is why that is the floor. The library entry works from both module systems.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
```js
|
|
19
|
+
import { evaluate } from '@20syldev/api'; // ESM
|
|
20
|
+
const { evaluate } = require('@20syldev/api'); // CommonJS
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The server entry loads its plugins asynchronously, so it must be imported rather than required:
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
```js
|
|
26
|
+
await import('@20syldev/api/server'); // from CommonJS
|
|
27
|
+
```
|
|
22
28
|
|
|
23
|
-
##
|
|
29
|
+
## Installation
|
|
24
30
|
|
|
25
31
|
```console
|
|
26
|
-
$ sudo apt install nodejs npm
|
|
27
32
|
$ npm install @20syldev/api
|
|
28
|
-
$ npm init
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
The root import always resolves to the latest version. Use a subpath (`/v1` to `/v5`) to pin a specific one.
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
// Latest version, equivalent to '@20syldev/api/v5'
|
|
41
|
+
import { color, evaluate, username } from '@20syldev/api';
|
|
42
|
+
|
|
43
|
+
// Or a specific version
|
|
44
|
+
import { color as colorV4 } from '@20syldev/api/v4';
|
|
45
|
+
|
|
46
|
+
// Evaluate a math expression
|
|
47
|
+
const expr = evaluate('sin(pi / 2) + sqrt(4)', 5);
|
|
48
|
+
console.log(expr.result); // 3
|
|
49
|
+
console.log(expr.expression); // 'sin(pi / 2) + sqrt(4)'
|
|
50
|
+
|
|
51
|
+
// Generate a random color, converted to every common notation
|
|
52
|
+
const c = color();
|
|
53
|
+
console.log(c.hex, c.rgb, c.hsl, c.hsv, c.hwb, c.cmyk);
|
|
54
|
+
|
|
55
|
+
// Generate a random username
|
|
56
|
+
const user = username();
|
|
57
|
+
console.log(user.username);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## What's inside
|
|
61
|
+
|
|
62
|
+
| Domain | Exports |
|
|
63
|
+
| ------------------ | ------------------------------------------------------------------------------------- |
|
|
64
|
+
| **Crypto** | `asymmetric` `symmetric` `hash` `jwt` `otp` `token` `password` `captcha` |
|
|
65
|
+
| **Math & data** | **`matrix`** **`algorithms`** `evaluate` `statistics` `convert` `csv` |
|
|
66
|
+
| **Text** | **`text`** **`encode`** **`validate`** `caseConvert` `levenshtein` `regex` `parseUrl` |
|
|
67
|
+
| **Graphics** | **`chart`** `barcode` `qrcode` `avatar` `placeholder` `color` `palette` |
|
|
68
|
+
| **Fake data** | `personal` `address` `credit` `username` `dice` `domain` `agent` |
|
|
69
|
+
| **Network & time** | `ip` `geo` `time` `cron` `hyperplanning` |
|
|
70
|
+
| **Stateful** | `chat` `tic_tac_toe` |
|
|
71
|
+
|
|
72
|
+
The six names in **bold** are namespaces — they group related functions, called as `namespace.method()`:
|
|
32
73
|
|
|
33
|
-
|
|
74
|
+
| Namespace | Functions |
|
|
75
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
76
|
+
| **`algorithms`** | `factorial` `fibonacci` `gcd` `isprime` `primefactors` `primelist` `roman` `anagram` `palindrome` `reverse` `bubblesort` |
|
|
77
|
+
| **`chart`** | `bar` `line` `pie` `donut` |
|
|
78
|
+
| **`encode`** | `base64encode` `base64decode` `urlencode` `urldecode` `binary` `unbinary` `morse` `unmorse` `caesar` `rot13` |
|
|
79
|
+
| **`matrix`** | `add` `subtract` `multiply` `scalar` `transpose` `determinant` `inverse` `identity` |
|
|
80
|
+
| **`text`** | `lorem` `slug` `stats` `number` |
|
|
81
|
+
| **`validate`** | `email` `iban` `luhn` |
|
|
34
82
|
|
|
35
|
-
|
|
83
|
+
Everything else is a plain function, called directly.
|
|
36
84
|
|
|
37
|
-
|
|
85
|
+
Two names differ from their HTTP endpoint, because `case` and `url` collide with reserved or global identifiers:
|
|
86
|
+
|
|
87
|
+
| Endpoint | Import |
|
|
88
|
+
| -------- | ------------- |
|
|
89
|
+
| `/case` | `caseConvert` |
|
|
90
|
+
| `/url` | `parseUrl` |
|
|
91
|
+
|
|
92
|
+
> _`personal` generates **fake** profiles (names, emails, jobs, addresses) for seeding and testing — it holds no real data._
|
|
93
|
+
|
|
94
|
+
## Examples
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
import {
|
|
98
|
+
asymmetric,
|
|
99
|
+
caseConvert,
|
|
100
|
+
chart,
|
|
101
|
+
csv,
|
|
102
|
+
evaluate,
|
|
103
|
+
jwt,
|
|
104
|
+
matrix,
|
|
105
|
+
otp,
|
|
106
|
+
parseUrl,
|
|
107
|
+
symmetric,
|
|
108
|
+
validate,
|
|
109
|
+
} from '@20syldev/api/v5';
|
|
110
|
+
|
|
111
|
+
// RSA key generation, encryption and decryption
|
|
112
|
+
const { publicKey, privateKey } = asymmetric('keygen', {});
|
|
113
|
+
const { result: encrypted } = asymmetric('encrypt', { text: 'secret message', publicKey });
|
|
114
|
+
const { result: decrypted } = asymmetric('decrypt', { text: encrypted, privateKey });
|
|
115
|
+
console.log(decrypted); // 'secret message'
|
|
116
|
+
|
|
117
|
+
// AES-256-GCM symmetric encryption, key derived with scrypt
|
|
118
|
+
const { result: blob } = symmetric('encrypt', 'secret message', 'a-strong-key');
|
|
119
|
+
const { result: plain } = symmetric('decrypt', blob, 'a-strong-key');
|
|
120
|
+
console.log(plain); // 'secret message'
|
|
121
|
+
|
|
122
|
+
// TOTP secret, code generation and verification
|
|
123
|
+
const { secret, uri } = otp('secret', { label: 'alice', issuer: 'MyApp' });
|
|
124
|
+
const { code } = otp('generate', { secret });
|
|
125
|
+
const { valid } = otp('verify', { secret, code });
|
|
126
|
+
console.log(valid); // true
|
|
127
|
+
|
|
128
|
+
// Decode a JWT without verifying its signature (inspection only)
|
|
129
|
+
const { header, payload } = jwt('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.qfhpVR4l');
|
|
130
|
+
console.log(header.alg, payload.sub); // 'HS256' '1234567890'
|
|
131
|
+
|
|
132
|
+
// Evaluate a math expression, with a precision
|
|
133
|
+
const { result } = evaluate('log2(8) * (3 + pi)', 4);
|
|
134
|
+
console.log(result); // 18.4248
|
|
135
|
+
|
|
136
|
+
// Matrix product
|
|
137
|
+
const { result: product } = matrix.multiply(
|
|
138
|
+
[
|
|
139
|
+
[1, 2],
|
|
140
|
+
[3, 4],
|
|
141
|
+
],
|
|
142
|
+
[
|
|
143
|
+
[5, 6],
|
|
144
|
+
[7, 8],
|
|
145
|
+
],
|
|
146
|
+
);
|
|
147
|
+
console.log(product); // [[19, 22], [43, 50]]
|
|
148
|
+
|
|
149
|
+
// Render an SVG chart in memory
|
|
150
|
+
const { body: svg } = chart.bar(
|
|
151
|
+
{ labels: ['Jan', 'Feb', 'Mar'], datasets: [{ label: 'Sales', values: [120, 85, 200] }] },
|
|
152
|
+
{ width: 600, height: 300 },
|
|
153
|
+
);
|
|
154
|
+
// body holds a ready-to-serve SVG string
|
|
155
|
+
|
|
156
|
+
// Parse CSV into rows, or format rows back into CSV
|
|
157
|
+
const { rows } = csv('parse', { csv: 'name,age\nAlice,30\nBob,25' });
|
|
158
|
+
console.log(rows); // [{ name: 'Alice', age: '30' }, { name: 'Bob', age: '25' }]
|
|
159
|
+
|
|
160
|
+
// Convert between case styles
|
|
161
|
+
console.log(caseConvert('hello world', 'pascal').result); // 'HelloWorld'
|
|
162
|
+
|
|
163
|
+
// Split a URL into its components
|
|
164
|
+
const parsed = parseUrl('https://example.com/a/b?tag=x&tag=y#top');
|
|
165
|
+
console.log(parsed.host, parsed.port, parsed.params, parsed.fragment);
|
|
166
|
+
|
|
167
|
+
// Validate an email, an IBAN or a card number
|
|
168
|
+
console.log(validate.email('alice@example.com').valid); // true
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Running the server
|
|
172
|
+
|
|
173
|
+
From the installed package, one import mounts every version:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
import '@20syldev/api/server';
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
From a clone of the repository:
|
|
38
180
|
|
|
39
181
|
```console
|
|
40
182
|
$ npm run build && npm start
|
|
41
183
|
```
|
|
42
184
|
|
|
43
185
|
```console
|
|
44
|
-
> @20syldev/api@5.
|
|
186
|
+
> @20syldev/api@5.7.0 build
|
|
45
187
|
> tsc
|
|
46
188
|
|
|
47
|
-
> @20syldev/api@5.
|
|
189
|
+
> @20syldev/api@5.7.0 start
|
|
48
190
|
> node dist/app.js
|
|
49
191
|
|
|
50
192
|
API is running on
|
|
@@ -52,83 +194,65 @@ API is running on
|
|
|
52
194
|
- http://localhost:3000
|
|
53
195
|
```
|
|
54
196
|
|
|
55
|
-
|
|
197
|
+
For development with automatic reloading:
|
|
56
198
|
|
|
57
199
|
```console
|
|
58
200
|
$ npm run dev
|
|
59
201
|
```
|
|
60
202
|
|
|
61
|
-
|
|
203
|
+
Endpoints follow the `/:version/:endpoint` pattern — `GET /v5/color`, `GET /v5/evaluate?expr=2%2B2`, `POST /v5/matrix`. `GET /` lists the available versions, and `GET /:version` lists that version's endpoints with their parameters.
|
|
62
204
|
|
|
63
|
-
|
|
205
|
+
### Configuration
|
|
64
206
|
|
|
65
|
-
|
|
66
|
-
// Importer des modules spécifiques depuis la v5
|
|
67
|
-
import { evaluate, color, username } from '@20syldev/api/v5';
|
|
207
|
+
Every setting is optional and read from the environment; a `.env` file at the project root is loaded automatically. The server runs with none of them set.
|
|
68
208
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
209
|
+
| Variable | Default | Purpose |
|
|
210
|
+
| ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
211
|
+
| `PORT` | `3000` | Port the server listens on |
|
|
212
|
+
| `DOCS_URL` | — | Documentation base URL reported by `/`, `/:version` and `/:version/infos` |
|
|
213
|
+
| `REPO_URL` | — | Source repository reported by `/:version/infos` |
|
|
214
|
+
| `INSTANCE_CREATED` | — | Launch date reported by `/:version/infos` |
|
|
215
|
+
| `LOGS_TOKEN` | — | Value required in the `X-Logs-Token` header to read `/logs`; open while unset |
|
|
216
|
+
| `TRUSTED_PROXIES` | one hop | CIDR blocks separated by spaces or commas, or the `loopback` / `linklocal` / `uniquelocal` shorthands, for deployments behind a CDN or platform router |
|
|
217
|
+
| `GLOBAL_LIMIT` | `50000` | Requests per hour accepted across the whole instance |
|
|
218
|
+
| `DEFAULT_LIMIT` | `2000` | Per-client requests per hour |
|
|
219
|
+
| `DEFAULT_BURST` | `50` | Per-client requests per 10-second window |
|
|
73
220
|
|
|
74
|
-
|
|
75
|
-
const couleur = color();
|
|
76
|
-
console.log(`Couleur HEX: ${couleur.hex}`);
|
|
77
|
-
console.log(`Couleur RGB: ${couleur.rgb}`);
|
|
221
|
+
The three metadata variables are omitted from responses when unset, so an instance never advertises somebody else's documentation or repository.
|
|
78
222
|
|
|
79
|
-
|
|
80
|
-
const utilisateur = username();
|
|
81
|
-
console.log(`Nom d'utilisateur: ${utilisateur.username}`);
|
|
82
|
-
```
|
|
223
|
+
Higher quotas can be granted per client: `ADVANCED_`, `PRO_` and `BUSINESS_` variants of `_LIMIT` and `_BURST` define the tiers, and the matching `*_TOKEN_LIST` variables hold the space-separated bearer tokens that map to them. A client's tier and quota are reported by `GET /auth`.
|
|
83
224
|
|
|
84
|
-
|
|
225
|
+
> _Rate-limit counters live in process memory, so each instance of a multi-instance deployment counts separately._
|
|
85
226
|
|
|
86
|
-
|
|
87
|
-
| ---------------- | ------------------------------------------------------------------ |
|
|
88
|
-
| `npm run dev` | Serveur de développement avec rechargement automatique (tsx watch) |
|
|
89
|
-
| `npm run build` | Compilation TypeScript vers `dist/` |
|
|
90
|
-
| `npm start` | Démarrer le serveur de production (`node dist/app.js`) |
|
|
91
|
-
| `npm test` | Lancer les tests unitaires et d'intégration |
|
|
92
|
-
| `npm run lint` | Vérification ESLint |
|
|
93
|
-
| `npm run format` | Formatage avec Prettier |
|
|
94
|
-
| `npm run check` | Vérification TypeScript + ESLint |
|
|
227
|
+
## Versioning
|
|
95
228
|
|
|
96
|
-
|
|
229
|
+
| Version | Adds |
|
|
230
|
+
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
231
|
+
| `v1` | algorithms, captcha, color, convert, domain, personal, qrcode, username, token |
|
|
232
|
+
| `v2` | chat, hash, tic-tac-toe |
|
|
233
|
+
| `v3` | levenshtein, time, hyperplanning |
|
|
234
|
+
| `v4` | 24 endpoints — address, avatar, barcode, credit, cron, dice, encode, geo, ip, palette, password, placeholder, regex, statistics, text, validate… plus PATCH and DELETE routes |
|
|
235
|
+
| `v5` | case, evaluate, url, asymmetric, chart, csv, jwt, matrix, otp, symmetric |
|
|
97
236
|
|
|
98
|
-
|
|
99
|
-
import { asymmetric, chart, evaluate, matrix, otp, symmetric } from '@20syldev/api/v5';
|
|
237
|
+
`v4` and `v5` are fully typed. The package root always tracks the latest version.
|
|
100
238
|
|
|
101
|
-
|
|
102
|
-
const { publicKey, privateKey } = asymmetric('keygen', {});
|
|
103
|
-
const { result: encrypted } = asymmetric('encrypt', { text: 'message secret', publicKey });
|
|
104
|
-
const { result: decrypted } = asymmetric('decrypt', { text: encrypted, privateKey });
|
|
105
|
-
console.log(decrypted); // 'message secret'
|
|
239
|
+
> _`v1`, `v2` and `v3` share the same untyped JavaScript implementation and differ only by the endpoints they expose. They are **deprecated** and will be removed in `6.0.0` — new code should target `v5`._
|
|
106
240
|
|
|
107
|
-
|
|
108
|
-
const { result } = evaluate('log2(8) * (3 + pi)', 4);
|
|
109
|
-
console.log(result); // 18.4248
|
|
241
|
+
## Scripts
|
|
110
242
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
243
|
+
| Command | Description |
|
|
244
|
+
| ---------------- | ------------------------------------------------------- |
|
|
245
|
+
| `npm run dev` | Development server with automatic reloading (tsx watch) |
|
|
246
|
+
| `npm run build` | Compile TypeScript to `dist/` |
|
|
247
|
+
| `npm start` | Start the production server (`node dist/app.js`) |
|
|
248
|
+
| `npm test` | Run unit and integration tests |
|
|
249
|
+
| `npm run format` | Format with Prettier and auto-fix with ESLint |
|
|
250
|
+
| `npm run check` | TypeScript and ESLint verification |
|
|
114
251
|
|
|
115
|
-
|
|
116
|
-
const { body: svg } = chart.bar(
|
|
117
|
-
{ labels: ['Jan', 'Fév', 'Mar'], datasets: [{ label: 'Ventes', values: [120, 85, 200] }] },
|
|
118
|
-
{ width: 600, height: 300 },
|
|
119
|
-
);
|
|
120
|
-
// body contient une chaîne SVG prête à l'emploi
|
|
252
|
+
## Hosted instance
|
|
121
253
|
|
|
122
|
-
|
|
123
|
-
const { result: blob } = symmetric('encrypt', 'message secret', 'motdepasse');
|
|
124
|
-
const { result: plain } = symmetric('decrypt', blob, 'motdepasse');
|
|
125
|
-
console.log(plain); // 'message secret'
|
|
254
|
+
A public instance runs at [api.sylvain.sh](https://api.sylvain.sh), with the complete endpoint reference, request examples and usage guides on [docs.sylvain.sh](https://docs.sylvain.sh).
|
|
126
255
|
|
|
127
|
-
|
|
128
|
-
const { secret } = otp('secret', {});
|
|
129
|
-
const { code } = otp('generate', { secret });
|
|
130
|
-
const { valid } = otp('verify', { secret, code });
|
|
131
|
-
console.log(valid); // true
|
|
132
|
-
```
|
|
256
|
+
## License
|
|
133
257
|
|
|
134
|
-
|
|
258
|
+
BSD 3-Clause. See [LICENSE](LICENSE).
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare function envList(key: string): string[] | null;
|
|
2
|
+
export declare const env: {
|
|
3
|
+
PORT: number;
|
|
4
|
+
TRUSTED_PROXIES: string[] | null;
|
|
5
|
+
DOCS_URL: string;
|
|
6
|
+
REPO_URL: string;
|
|
7
|
+
INSTANCE_CREATED: string;
|
|
8
|
+
LOGS_TOKEN: string;
|
|
9
|
+
DEFAULT_LIMIT: number;
|
|
10
|
+
ADVANCED_LIMIT: number;
|
|
11
|
+
PRO_LIMIT: number;
|
|
12
|
+
BUSINESS_LIMIT: number;
|
|
13
|
+
GLOBAL_LIMIT: number;
|
|
14
|
+
DEFAULT_BURST: number;
|
|
15
|
+
ADVANCED_BURST: number;
|
|
16
|
+
PRO_BURST: number;
|
|
17
|
+
BUSINESS_BURST: number;
|
|
18
|
+
BUSINESS_TOKEN_LIST: string[];
|
|
19
|
+
PRO_TOKEN_LIST: string[];
|
|
20
|
+
ADVANCED_TOKEN_LIST: string[];
|
|
21
|
+
};
|
package/dist/config/env.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import dotenv from 'dotenv';
|
|
2
2
|
import { DEFAULT_PORT } from '../constants.js';
|
|
3
3
|
dotenv.config();
|
|
4
|
-
function envList(key) {
|
|
4
|
+
export function envList(key) {
|
|
5
5
|
const v = process.env[key];
|
|
6
|
-
|
|
6
|
+
if (!v || v === 'undefined')
|
|
7
|
+
return null;
|
|
8
|
+
const items = v.split(/[\s,]+/).filter(Boolean);
|
|
9
|
+
return items.length ? items : null;
|
|
7
10
|
}
|
|
8
11
|
function envNumber(key, fallback) {
|
|
9
12
|
const v = parseInt(process.env[key] ?? '', 10);
|
|
@@ -11,6 +14,11 @@ function envNumber(key, fallback) {
|
|
|
11
14
|
}
|
|
12
15
|
export const env = {
|
|
13
16
|
PORT: envNumber('PORT', DEFAULT_PORT),
|
|
17
|
+
TRUSTED_PROXIES: envList('TRUSTED_PROXIES'),
|
|
18
|
+
DOCS_URL: (process.env.DOCS_URL ?? '').replace(/\/+$/, ''),
|
|
19
|
+
REPO_URL: process.env.REPO_URL ?? '',
|
|
20
|
+
INSTANCE_CREATED: process.env.INSTANCE_CREATED ?? '',
|
|
21
|
+
LOGS_TOKEN: process.env.LOGS_TOKEN ?? '',
|
|
14
22
|
DEFAULT_LIMIT: envNumber('DEFAULT_LIMIT', 2000),
|
|
15
23
|
ADVANCED_LIMIT: envNumber('ADVANCED_LIMIT', 3500),
|
|
16
24
|
PRO_LIMIT: envNumber('PRO_LIMIT', 6000),
|
package/dist/config/env.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,UAAU,OAAO,CAAC,GAAW;IAC/B,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAEzC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC;AAED,SAAS,SAAS,CAAC,GAAW,EAAE,QAAgB;IAC5C,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACf,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC;IACrC,eAAe,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAE3C,QAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAC1D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE;IACpC,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE;IACpD,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE;IAExC,aAAa,EAAE,SAAS,CAAC,eAAe,EAAE,IAAI,CAAC;IAC/C,cAAc,EAAE,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC;IACjD,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC;IACvC,cAAc,EAAE,SAAS,CAAC,gBAAgB,EAAE,KAAK,CAAC;IAClD,YAAY,EAAE,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC;IAE9C,aAAa,EAAE,SAAS,CAAC,eAAe,EAAE,EAAE,CAAC;IAC7C,cAAc,EAAE,SAAS,CAAC,gBAAgB,EAAE,EAAE,CAAC;IAC/C,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC;IACtC,cAAc,EAAE,SAAS,CAAC,gBAAgB,EAAE,GAAG,CAAC;IAEhD,mBAAmB,EAAE,OAAO,CAAC,qBAAqB,CAAC,IAAI,EAAE;IACzD,cAAc,EAAE,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE;IAC/C,mBAAmB,EAAE,OAAO,CAAC,qBAAqB,CAAC,IAAI,EAAE;CAC5D,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limit plans configuration.
|
|
3
|
+
*
|
|
4
|
+
* hourly — max requests per hour (matches pricing page)
|
|
5
|
+
* burst — max requests per 10-second window
|
|
6
|
+
* tokens — list of bearer tokens for this tier
|
|
7
|
+
*
|
|
8
|
+
* To adjust limits, edit the values below or override via .env:
|
|
9
|
+
* DEFAULT_LIMIT, ADVANCED_LIMIT, PRO_LIMIT, BUSINESS_LIMIT
|
|
10
|
+
* DEFAULT_BURST, ADVANCED_BURST, PRO_BURST, BUSINESS_BURST
|
|
11
|
+
*/
|
|
12
|
+
export interface Plan {
|
|
13
|
+
hourly: number;
|
|
14
|
+
burst: number;
|
|
15
|
+
tokens: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare const plans: Record<string, Plan>;
|
|
18
|
+
export declare const globalLimit: number;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the plan matching a bearer token, or 'default'.
|
|
21
|
+
* Returns null if the token is provided but invalid.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getPlan(token: string): {
|
|
24
|
+
name: string;
|
|
25
|
+
plan: Plan;
|
|
26
|
+
} | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as apiv3 from '../modules/v3.js';
|
|
2
|
+
import * as apiv4 from '../modules/v4.js';
|
|
3
|
+
import * as apiv5 from '../modules/v5.js';
|
|
4
|
+
export interface Endpoint {
|
|
5
|
+
name: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
children?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export interface VersionConfig {
|
|
10
|
+
endpoints: {
|
|
11
|
+
get: Endpoint[];
|
|
12
|
+
post: Endpoint[];
|
|
13
|
+
patch?: Endpoint[];
|
|
14
|
+
delete?: Endpoint[];
|
|
15
|
+
};
|
|
16
|
+
modules: typeof apiv3 | typeof apiv4 | typeof apiv5;
|
|
17
|
+
}
|
|
18
|
+
export declare const versions: Record<string, VersionConfig>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const APP_VERSION: string;
|
|
2
|
+
export declare const DEFAULT_PORT = 3000;
|
|
3
|
+
export declare const START_TIME: number;
|
|
4
|
+
export declare const GAME_CLEANUP_TTL = 600000;
|
|
5
|
+
export declare const MAX_LOG_ENTRIES = 1000;
|
|
6
|
+
export declare const RATE_LIMIT_WINDOW = 10000;
|
|
7
|
+
export declare const RATE_LIMIT_MAX = 50;
|
|
8
|
+
export declare const SESSION_TTL = 3600000;
|
|
9
|
+
export declare const STATUS_MESSAGES: Record<number, string>;
|
|
10
|
+
export declare const EARTH_RADIUS_KM = 6371;
|
|
11
|
+
export declare const MAX_ADDRESS_COUNT = 10;
|
|
12
|
+
export declare const MAX_CHART_DATASETS = 5;
|
|
13
|
+
export declare const MAX_CHART_LABELS = 20;
|
|
14
|
+
export declare const MAX_CSV_LENGTH = 50000;
|
|
15
|
+
export declare const MAX_CSV_ROWS = 1000;
|
|
16
|
+
export declare const MAX_CREDIT_COUNT = 10;
|
|
17
|
+
export declare const MAX_COUNTDOWN_YEARS = 100;
|
|
18
|
+
export declare const MAX_CRON_ITERATIONS: number;
|
|
19
|
+
export declare const MAX_CRON_RESULTS = 20;
|
|
20
|
+
export declare const MAX_EXPR_DEPTH = 100;
|
|
21
|
+
export declare const MAX_EXPR_LENGTH = 500;
|
|
22
|
+
export declare const MAX_FACTORIAL = 170;
|
|
23
|
+
export declare const MAX_GCD_VALUE = 100000;
|
|
24
|
+
export declare const MAX_JWT_LENGTH = 8192;
|
|
25
|
+
export declare const MAX_LEVENSHTEIN_LENGTH = 1000;
|
|
26
|
+
export declare const MIN_PASSWORD_LENGTH = 8;
|
|
27
|
+
export declare const MAX_PASSWORD_LENGTH = 128;
|
|
28
|
+
export declare const MAX_PASSWORD_COUNT = 20;
|
|
29
|
+
export declare const MAX_MATRIX_SIZE = 20;
|
|
30
|
+
export declare const MAX_PATTERN_LENGTH = 200;
|
|
31
|
+
export declare const MAX_PRIME_LIST = 10000;
|
|
32
|
+
export declare const MAX_QRCODE_LOGO_BYTES: number;
|
|
33
|
+
export declare const MAX_REGEX_MATCHES = 100;
|
|
34
|
+
export declare const MAX_RSA_MODULUS = 4096;
|
|
35
|
+
export declare const MAX_STRING_LENGTH = 1000;
|
|
36
|
+
export declare const MIN_TOKEN_LENGTH = 12;
|
|
37
|
+
export declare const MAX_TOKEN_LENGTH = 4096;
|
|
38
|
+
export declare const MAX_URL_LENGTH = 2048;
|
|
39
|
+
export declare const ROMAN_VALUES: [number, string][];
|
package/dist/constants.js
CHANGED
|
@@ -2,7 +2,6 @@ import pkg from '../package.json' with { type: 'json' };
|
|
|
2
2
|
// App
|
|
3
3
|
export const APP_VERSION = pkg.version;
|
|
4
4
|
export const DEFAULT_PORT = 3000;
|
|
5
|
-
export const DOCS_URL = 'https://docs.sylvain.sh';
|
|
6
5
|
export const START_TIME = Date.now();
|
|
7
6
|
// Server
|
|
8
7
|
export const GAME_CLEANUP_TTL = 600_000;
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAExD,MAAM;AACN,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;AACjC,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware/cors.js
CHANGED
|
@@ -2,10 +2,11 @@ import cors from 'cors';
|
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { env } from '../config/env.js';
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = dirname(__filename);
|
|
7
8
|
export function setupCors(app) {
|
|
8
|
-
app.set('trust proxy', 1);
|
|
9
|
+
app.set('trust proxy', env.TRUSTED_PROXIES ?? 1);
|
|
9
10
|
app.use(cors({ methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'] }));
|
|
10
11
|
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
|
|
11
12
|
app.use(express.json({ limit: '10kb' }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAAyB,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,MAAM,UAAU,SAAS,CAAC,GAAY;IAClC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAAyB,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAEvC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,MAAM,UAAU,SAAS,CAAC,GAAY;IAClC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC;IAEjD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1E,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/D,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAEzC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxB,GAAG,CAAC,SAAS,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;QACnD,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACzC,IAAI,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3F,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;AACtF,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
2
|
+
export declare const logger: import("@20syldev/logger.ts").Logger;
|
|
3
|
+
/**
|
|
4
|
+
* Replaces a secret carried in the path with a placeholder.
|
|
5
|
+
*
|
|
6
|
+
* @param path - Request path, without its query string
|
|
7
|
+
* @returns The path, with a trailing credential segment masked
|
|
8
|
+
*/
|
|
9
|
+
export declare function redactPath(path: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Strips query string values from a URL, keeping the path and parameter names.
|
|
12
|
+
*
|
|
13
|
+
* GET endpoints carry user input in the query string — text passed to /encode,
|
|
14
|
+
* card numbers and IBANs passed to /validate, URLs passed to /qrcode — and the
|
|
15
|
+
* log buffer is readable over /logs. Names alone keep the trace useful for
|
|
16
|
+
* debugging without retaining anybody's payload.
|
|
17
|
+
*
|
|
18
|
+
* Names are kept percent-encoded on purpose. Decoding them would let a caller
|
|
19
|
+
* write raw newlines and ANSI escapes into the operator's terminal and into the
|
|
20
|
+
* /logs buffer, and would expose the theme's {placeholder} syntax.
|
|
21
|
+
*
|
|
22
|
+
* @param originalUrl - Request URL, with or without a query string
|
|
23
|
+
* @returns The path, followed by the parameter names when the URL had any
|
|
24
|
+
*/
|
|
25
|
+
export declare function redactQuery(originalUrl: string): string;
|
|
26
|
+
export declare function loggerMiddleware(req: Request, res: Response, next: NextFunction): void;
|
|
@@ -5,6 +5,39 @@ export const logger = createLogger({
|
|
|
5
5
|
console: true,
|
|
6
6
|
theme: 'colored',
|
|
7
7
|
});
|
|
8
|
+
const SECRET_PATH = /^(\/v\d+\/(?:chat|tic-tac-toe))\/[^/]+/;
|
|
9
|
+
/**
|
|
10
|
+
* Replaces a secret carried in the path with a placeholder.
|
|
11
|
+
*
|
|
12
|
+
* @param path - Request path, without its query string
|
|
13
|
+
* @returns The path, with a trailing credential segment masked
|
|
14
|
+
*/
|
|
15
|
+
export function redactPath(path) {
|
|
16
|
+
return path.replace(SECRET_PATH, '$1/[redacted]');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Strips query string values from a URL, keeping the path and parameter names.
|
|
20
|
+
*
|
|
21
|
+
* GET endpoints carry user input in the query string — text passed to /encode,
|
|
22
|
+
* card numbers and IBANs passed to /validate, URLs passed to /qrcode — and the
|
|
23
|
+
* log buffer is readable over /logs. Names alone keep the trace useful for
|
|
24
|
+
* debugging without retaining anybody's payload.
|
|
25
|
+
*
|
|
26
|
+
* Names are kept percent-encoded on purpose. Decoding them would let a caller
|
|
27
|
+
* write raw newlines and ANSI escapes into the operator's terminal and into the
|
|
28
|
+
* /logs buffer, and would expose the theme's {placeholder} syntax.
|
|
29
|
+
*
|
|
30
|
+
* @param originalUrl - Request URL, with or without a query string
|
|
31
|
+
* @returns The path, followed by the parameter names when the URL had any
|
|
32
|
+
*/
|
|
33
|
+
export function redactQuery(originalUrl) {
|
|
34
|
+
const [path = '', ...rest] = originalUrl.split('?');
|
|
35
|
+
const query = rest.join('?');
|
|
36
|
+
if (!query)
|
|
37
|
+
return redactPath(path);
|
|
38
|
+
const names = [...new Set(query.split('&').map((pair) => pair.split('=')[0] ?? ''))].filter(Boolean);
|
|
39
|
+
return names.length ? `${redactPath(path)}?${names.join('&')}` : redactPath(path);
|
|
40
|
+
}
|
|
8
41
|
export function loggerMiddleware(req, res, next) {
|
|
9
42
|
if (req.method === 'HEAD') {
|
|
10
43
|
next();
|
|
@@ -19,7 +52,7 @@ export function loggerMiddleware(req, res, next) {
|
|
|
19
52
|
res.on('finish', () => {
|
|
20
53
|
const status = res.statusCode === 304 ? 200 : res.statusCode;
|
|
21
54
|
const duration = `${Date.now() - startTime}ms`;
|
|
22
|
-
logger.log({ method: req.method, url: req.originalUrl, status, duration, platform });
|
|
55
|
+
logger.log({ method: req.method, url: redactQuery(req.originalUrl), status, duration, platform });
|
|
23
56
|
});
|
|
24
57
|
next();
|
|
25
58
|
}
|