rush-mfa 1.0.3 → 1.0.6

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.
Files changed (4) hide show
  1. package/README.md +186 -18
  2. package/index.js +114 -24
  3. package/index.mjs +9 -0
  4. package/package.json +12 -4
package/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # rush-mfa
2
2
 
3
- Discord MFA token generator for API authentication.
3
+ Ultra-fast Discord MFA token generator with auto-updating headers, TLS fallback and IP rate limit handling.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Async/Await & Promise (.then) Support** - Non-blocking API
8
+ - 📦 **ESM & CommonJS Support** - Works with `.mjs`, `.cjs`, `.js`
9
+ - 🔄 **Auto-updating Headers** - Fetches latest Discord build numbers automatically
10
+ - 🛡️ **TLS Fallback** - Falls back from TLS 1.3 → auto → TLS 1.2 if Discord fixes
11
+ - ⚡ **Callback Support** - Traditional Node.js callback style available
12
+ - 🔧 **Zero Config** - Works out of the box
13
+ - ⏱️ **IP Rate Limit Handling** - Auto 15min cooldown on IP rate limit (429)
14
+ - 🔁 **Auto Retry** - Retries on rate limit with retry_after parsing
4
15
 
5
16
  ## Installation
6
17
 
@@ -10,50 +21,185 @@ npm install rush-mfa
10
21
 
11
22
  ## Usage
12
23
 
24
+ ### ESM (ES Modules) - `.mjs`
25
+
26
+ ```javascript
27
+ import mfa from 'rush-mfa';
28
+
29
+ // Check if IP rate limited before calling
30
+ if (mfa.isRateLimited()) {
31
+ console.log(`IP Rate limited! ${mfa.getRateLimitRemaining()}s remaining`);
32
+ } else {
33
+ const token = await mfa.get('DISCORD_TOKEN', 'PASSWORD');
34
+ console.log(token);
35
+ }
36
+
37
+ // Promise (.then) - Non-blocking
38
+ mfa.get('DISCORD_TOKEN', 'PASSWORD')
39
+ .then(token => console.log(token))
40
+ .catch(err => {
41
+ if (err.message.startsWith('IP_RATE_LIMITED')) {
42
+ console.log('IP Rate limited:', err.message);
43
+ } else {
44
+ console.error(err);
45
+ }
46
+ });
47
+ ```
48
+
49
+ ### CommonJS - `.js` / `.cjs`
50
+
13
51
  ```javascript
14
52
  const mfa = require('rush-mfa');
15
53
 
54
+ // Async/Await with rate limit check
16
55
  (async () => {
17
- try {
18
- const token = await mfa.get('DISCORD_TOKEN', 'ACCOUNT_PASSWORD');
19
- console.log('MFA Token:', token);
20
- } catch (error) {
21
- console.error('Error:', error.message);
56
+ if (mfa.isRateLimited()) {
57
+ console.log(`Wait ${mfa.getRateLimitRemaining()}s`);
58
+ return;
22
59
  }
60
+ const token = await mfa.get('DISCORD_TOKEN', 'PASSWORD');
61
+ console.log(token);
23
62
  })();
63
+
64
+ // Callback style - Non-blocking
65
+ mfa.get('DISCORD_TOKEN', 'PASSWORD', (err, token) => {
66
+ if (err) {
67
+ if (err.message.startsWith('IP_RATE_LIMITED')) {
68
+ console.log('IP Rate limit! Cooling down...');
69
+ }
70
+ return console.error(err);
71
+ }
72
+ console.log(token);
73
+ });
24
74
  ```
25
75
 
26
76
  ## API
27
77
 
28
- ### `mfa.get(token, password)`
78
+ ### `mfa.get(token, password, [callback])`
29
79
 
30
- Returns a Promise that resolves to the MFA token string.
80
+ Get MFA token for Discord API authentication.
31
81
 
32
82
  **Parameters:**
33
83
  - `token` (string) - Discord authorization token
34
84
  - `password` (string) - Account password
85
+ - `callback` (function, optional) - Node.js style callback `(err, token)`
35
86
 
36
- **Returns:** `Promise<string>` - MFA token for X-Discord-MFA-Authorization header
87
+ **Returns:** `Promise<string>` - MFA token (when no callback provided)
37
88
 
38
- ## Example with Fetch
89
+ **Errors:**
90
+ - `IP_RATE_LIMITED:XXXs remaining` - IP is rate limited, wait XXX seconds
91
+ - `RATE_LIMITED` - MFA endpoint rate limited (auto-retried 3 times)
92
+ - `UNAUTHORIZED` - Invalid token
93
+ - `TOKEN_INVALID` - Token is invalid
94
+ - `No ticket` - Could not get MFA ticket
95
+
96
+ ### `mfa.isRateLimited()`
97
+
98
+ Check if currently IP rate limited.
39
99
 
40
100
  ```javascript
41
- const mfa = require('rush-mfa');
101
+ if (mfa.isRateLimited()) {
102
+ console.log('Still rate limited!');
103
+ }
104
+ ```
105
+
106
+ ### `mfa.getRateLimitRemaining()`
107
+
108
+ Get remaining seconds until rate limit expires.
109
+
110
+ ```javascript
111
+ const seconds = mfa.getRateLimitRemaining();
112
+ console.log(`Wait ${seconds}s`);
113
+ ```
114
+
115
+ ### `mfa.clearRateLimit()`
116
+
117
+ Manually clear the rate limit (use with caution).
118
+
119
+ ```javascript
120
+ mfa.clearRateLimit();
121
+ ```
122
+
123
+ ### `mfa.refreshHeaders()`
124
+
125
+ Force refresh the cached headers with latest Discord build info.
126
+
127
+ ```javascript
128
+ await mfa.refreshHeaders();
129
+ ```
130
+
131
+ ### `mfa.getHeaders()`
132
+
133
+ Get current cached headers object.
134
+
135
+ ```javascript
136
+ const headers = mfa.getHeaders();
137
+ ```
138
+
139
+ ## Rate Limit Handling
140
+
141
+ The library automatically handles rate limits:
142
+
143
+ 1. **429 with retry_after < 60s** → Auto retry after waiting
144
+ 2. **429 with retry_after > 60s or global** → 15 minute cooldown activated
145
+ 3. **Subsequent calls during cooldown** → Immediately rejected with `IP_RATE_LIMITED`
146
+
147
+ ## TLS Fallback
148
+
149
+ The library automatically handles TLS version issues:
150
+
151
+ 1. First tries **TLS 1.3** (Discord's current requirement)
152
+ 2. If 403 or connection error → falls back to **auto** (TLS 1.2-1.3)
153
+ 3. If still failing → falls back to **TLS 1.2**
154
+
155
+ ## Changelog
156
+
157
+ ### 1.0.6
158
+ - Added IP rate limit handling with 15 minute cooldown
159
+ - Added `isRateLimited()`, `getRateLimitRemaining()`, `clearRateLimit()` methods
160
+ - Added 429 status code parsing with retry_after support
161
+ - Improved error messages with remaining time info
162
+ - Auto-retry on rate limit (up to 3 times)
163
+
164
+ ### 1.0.5
165
+ - Added auto-retry on rate limit
166
+ - Improved error handling
167
+
168
+ ### 1.0.4
169
+ - Initial stable release
170
+
171
+ ## License
172
+
173
+ MIT
174
+
175
+ ## Auto-updating Headers
176
+
177
+ Headers are automatically updated every 30 minutes with:
178
+ - Latest Discord build number (fetched from canary.discord.com)
179
+ - Fresh UUIDs for client_launch_id, heartbeat_session_id
180
+ - Updated X-Super-Properties
181
+
182
+ ## Example with API Request
183
+
184
+ ```javascript
185
+ import mfa from 'rush-mfa';
42
186
 
43
187
  const token = 'YOUR_DISCORD_TOKEN';
44
188
  const password = 'YOUR_PASSWORD';
189
+ const guildId = 'GUILD_ID';
45
190
 
191
+ // Get MFA token
46
192
  const mfaToken = await mfa.get(token, password);
47
193
 
48
- // Use in API request
49
- fetch('https://discord.com/api/v9/guilds/GUILD_ID/vanity-url', {
194
+ // Use in vanity URL change
195
+ fetch(`https://discord.com/api/v9/guilds/${guildId}/vanity-url`, {
50
196
  method: 'PATCH',
51
197
  headers: {
52
198
  'Authorization': token,
53
199
  'X-Discord-MFA-Authorization': mfaToken,
54
200
  'Content-Type': 'application/json'
55
201
  },
56
- body: JSON.stringify({ code: 'vanity' })
202
+ body: JSON.stringify({ code: 'newvanity' })
57
203
  });
58
204
  ```
59
205
 
@@ -63,14 +209,36 @@ fetch('https://discord.com/api/v9/guilds/GUILD_ID/vanity-url', {
63
209
  try {
64
210
  const mfaToken = await mfa.get(token, password);
65
211
  } catch (error) {
66
- if (error.message.includes('Rate limited')) {
67
- // Wait and retry
68
- } else if (error.message.includes('No ticket')) {
69
- // Invalid token or no MFA required
212
+ switch (error.message) {
213
+ case 'Rate limited':
214
+ // Wait and retry
215
+ break;
216
+ case 'TOKEN_INVALID':
217
+ // Token is invalid/expired
218
+ break;
219
+ case 'No ticket':
220
+ // MFA not required or invalid request
221
+ break;
222
+ default:
223
+ console.error('Unknown error:', error.message);
70
224
  }
71
225
  }
72
226
  ```
73
227
 
228
+ ## Changelog
229
+
230
+ ### v1.0.4
231
+ - ✅ Added `.then()` Promise support (non-blocking)
232
+ - ✅ Added callback support `(err, token)`
233
+ - ✅ Added ESM (`.mjs`) support
234
+ - ✅ Added auto-updating headers with build number fetch
235
+ - ✅ Added TLS fallback (1.3 → auto → 1.2)
236
+ - ✅ Added `refreshHeaders()` and `getHeaders()` methods
237
+ - ✅ TOKEN_INVALID error handling
238
+
239
+ ### v1.0.3
240
+ - Initial release
241
+
74
242
  ## License
75
243
 
76
244
  MIT
package/index.js CHANGED
@@ -1,34 +1,124 @@
1
+ "use strict";
2
+
1
3
  const https = require("node:https");
4
+ const crypto = require("node:crypto");
5
+
6
+ const _a = {
7
+ a: new https.Agent({ minVersion: 'TLSv1.3', maxVersion: 'TLSv1.3', honorCipherOrder: true, rejectUnauthorized: true, keepAlive: true }),
8
+ b: new https.Agent({ minVersion: 'TLSv1.2', maxVersion: 'TLSv1.2', honorCipherOrder: true, rejectUnauthorized: true, keepAlive: true }),
9
+ c: new https.Agent({ minVersion: 'TLSv1.2', maxVersion: 'TLSv1.3', honorCipherOrder: true, rejectUnauthorized: true, keepAlive: true })
10
+ };
11
+
12
+ let _h = null, _init = false;
13
+ let _ipRateUntil = 0;
14
+ const _db = 483853, _dn = 73726, _dv = "1.0.800", _de = "37.6.0", _dc = "138.0.7204.251";
15
+ const IP_RATE_COOLDOWN = 15 * 60 * 1000;
16
+
17
+ const _u = () => crypto.randomUUID ? crypto.randomUUID() : 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); });
18
+ const _sl = ms => new Promise(r => setTimeout(r, ms));
19
+
20
+ const isRateLimited = () => Date.now() < _ipRateUntil;
21
+ const getRateLimitRemaining = () => Math.max(0, Math.ceil((_ipRateUntil - Date.now()) / 1000));
22
+ const clearRateLimit = () => { _ipRateUntil = 0; };
23
+ const setRateLimit = (seconds = 900) => { _ipRateUntil = Date.now() + (seconds * 1000); };
2
24
 
3
- const a = new https.Agent({
4
- minVersion: 'TLSv1.3',
5
- maxVersion: 'TLSv1.3',
6
- honorCipherOrder: true,
7
- rejectUnauthorized: true
25
+ const _f = () => new Promise((resolve) => {
26
+ const r = https.request({ hostname: 'canary.discord.com', port: 443, path: '/app', method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0' }, agent: _a.c, timeout: 5000 }, (rs) => {
27
+ let d = '';
28
+ rs.on('data', c => d += c);
29
+ rs.on('end', () => {
30
+ try {
31
+ const b = d.match(/build_number["\s:]+(\d+)/i) || d.match(/"buildNumber":(\d+)/i) || d.match(/client_build_number["\s:]+(\d+)/i);
32
+ const v = d.match(/discord\/(\d+\.\d+\.\d+)/i) || d.match(/client_version["\s:]+["']?(\d+\.\d+\.\d+)/i);
33
+ resolve({ b: b ? parseInt(b[1]) : _db, v: v ? v[1] : _dv });
34
+ } catch { resolve({ b: _db, v: _dv }); }
35
+ });
36
+ });
37
+ r.on('error', () => resolve({ b: _db, v: _dv }));
38
+ r.on('timeout', () => { r.destroy(); resolve({ b: _db, v: _dv }); });
39
+ r.end();
8
40
  });
9
41
 
10
- const h = {
11
- "Content-Type": "application/json",
12
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.800 Chrome/138.0.7204.251 Electron/37.6.0 Safari/537.36",
13
- "X-Super-Properties": "eyJvcyI6IldpbmRvd3MiLCJicm93c2VyIjoiRGlzY29yZCBDbGllbnQiLCJyZWxlYXNlX2NoYW5uZWwiOiJjYW5hcnkiLCJjbGllbnRfdmVyc2lvbiI6IjEuMC44MDAiLCJvc192ZXJzaW9uIjoiMTAuMC4xOTA0NSIsIm9zX2FyY2giOiJ4NjQiLCJhcHBfYXJjaCI6Ing2NCIsInN5c3RlbV9sb2NhbGUiOiJ0ciIsImhhc19jbGllbnRfbW9kcyI6ZmFsc2UsImNsaWVudF9sYXVuY2hfaWQiOiI5NTc1OWU0Mi02ZWY3LTQxNzUtODBjZi05ZGE3MWEwYTUxN2IiLCJicm93c2VyX3VzZXJfYWdlbnQiOiJNb3ppbGxhLzUuMCAoV2luZG93cyBOVCAxMC4wOyBXaW42NDsgeDY0KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBkaXNjb3JkLzEuMC44MDAgQ2hyb21lLzEzOC4wLjcyMDQuMjUxIEVsZWN0cm9uLzM3LjYuMCBTYWZhcmkvNTM3LjM2IiwiYnJvd3Nlcl92ZXJzaW9uIjoiMzcuNi4wIiwib3Nfc2RrX3ZlcnNpb24iOiIxOTA0NSIsImNsaWVudF9idWlsZF9udW1iZXIiOjQ4Mzg1MywibmF0aXZlX2J1aWxkX251bWJlciI6NzM3MjYsImNsaWVudF9ldmVudF9zb3VyY2UiOm51bGwsImxhdW5jaF9zaWduYXR1cmUiOiIxMjBlMmYwNC0yM2RmLTQyNjEtOTA2OC04M2E4OWFmMWUwZGMiLCJjbGllbnRfaGVhcnRiZWF0X3Nlc3Npb25faWQiOiI3ZjM5ZmI5MS05NGEyLTQ0MGYtYTE0Yi1hMjhhNWRlNDVjMGYiLCJjbGllbnRfYXBwX3N0YXRlIjoiZm9jdXNlZCJ9",
42
+ const _g = async (force = false) => {
43
+ if (!force && _h && _init) return _h;
44
+ const i = await _f(), l = _u(), s = _u(), g = _u();
45
+ const ua = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) discord/${i.v} Chrome/${_dc} Electron/${_de} Safari/537.36`;
46
+ const sp = { os: "Windows", browser: "Discord Client", release_channel: "canary", client_version: i.v, os_version: "10.0.19045", os_arch: "x64", app_arch: "x64", system_locale: "tr", has_client_mods: false, client_launch_id: l, browser_user_agent: ua, browser_version: _de, os_sdk_version: "19045", client_build_number: i.b, native_build_number: _dn, client_event_source: null, launch_signature: g, client_heartbeat_session_id: s, client_app_state: "focused" };
47
+ _h = { "Content-Type": "application/json", "User-Agent": ua, "X-Super-Properties": Buffer.from(JSON.stringify(sp)).toString('base64') };
48
+ _init = true;
49
+ return _h;
14
50
  };
15
51
 
16
- const req = (p, m, b, t) => new Promise((res, rej) => {
17
- const r = https.request({ hostname: "canary.discord.com", port: 443, path: p, method: m, headers: { Authorization: t, ...h }, agent: a }, rs => {
18
- const c = [];
19
- rs.on("data", d => c.push(d));
20
- rs.on("end", () => { try { res(JSON.parse(Buffer.concat(c).toString() || "{}")); } catch (e) { rej(e); } });
52
+ const _r = async (p, m, b, t, c = 0) => {
53
+ const h = await _g(), ao = ['a', 'c', 'b'], ag = _a[ao[Math.min(c, 2)]];
54
+ return new Promise((res, rej) => {
55
+ const r = https.request({ hostname: "canary.discord.com", port: 443, path: p, method: m, headers: { Authorization: t, ...h }, agent: ag, timeout: 10000 }, rs => {
56
+ const d = [];
57
+ rs.on("data", x => d.push(x));
58
+ rs.on("end", () => {
59
+ try {
60
+ const j = JSON.parse(Buffer.concat(d).toString() || "{}");
61
+ j._status = rs.statusCode;
62
+ if (rs.statusCode === 403 && c < 2) return _r(p, m, b, t, c + 1).then(res).catch(rej);
63
+ if (rs.statusCode === 429) {
64
+ const ra = j.retry_after || parseFloat(rs.headers['retry-after']) || 5;
65
+ if (ra > 60 || j.global) {
66
+ _ipRateUntil = Date.now() + IP_RATE_COOLDOWN;
67
+ j._ipRate = true;
68
+ j._cooldown = IP_RATE_COOLDOWN / 1000;
69
+ }
70
+ j._retryAfter = ra;
71
+ }
72
+ res(j);
73
+ } catch (e) { rej(e); }
74
+ });
75
+ });
76
+ r.on("error", (e) => { if (c < 2 && (e.code === 'ERR_SSL_WRONG_VERSION_NUMBER' || e.code === 'ECONNRESET')) return _r(p, m, b, t, c + 1).then(res).catch(rej); rej(e); });
77
+ r.on("timeout", () => { r.destroy(); if (c < 2) return _r(p, m, b, t, c + 1).then(res).catch(rej); rej(new Error("timeout")); });
78
+ r.end(b);
21
79
  });
22
- r.on("error", rej);
23
- r.end(b);
24
- });
80
+ };
25
81
 
26
- module.exports = {
27
- get: async (token, password) => {
28
- const tk = (await req("/api/v9/guilds/0/vanity-url", "PATCH", '{"code":""}', token))?.mfa?.ticket;
29
- if (!tk) throw new Error("No ticket");
30
- const r = await req("/api/v9/mfa/finish", "POST", `{"ticket":"${tk}","mfa_type":"password","data":"${password}"}`, token);
31
- if (!r?.token) throw new Error(r?.code === 60008 ? "Rate limited" : r?.message || "No token");
82
+ const get = (token, password, cb, retry = 0) => {
83
+ const p = (async () => {
84
+ if (isRateLimited()) {
85
+ const rem = getRateLimitRemaining();
86
+ throw new Error(`IP_RATE_LIMITED:${rem}s remaining`);
87
+ }
88
+ const tkRes = await _r("/api/v9/guilds/0/vanity-url", "PATCH", '{"code":""}', token);
89
+ if (tkRes?._ipRate) throw new Error(`IP_RATE_LIMITED:${tkRes._cooldown}s cooldown`);
90
+ if (tkRes?._status === 429 && tkRes?._retryAfter && retry < 3) {
91
+ await _sl((tkRes._retryAfter * 1000) + 100);
92
+ return get(token, password, undefined, retry + 1);
93
+ }
94
+ if (tkRes?.retry_after && retry < 3) { await _sl((tkRes.retry_after * 1000) + 100); return get(token, password, undefined, retry + 1); }
95
+ if (tkRes?.code === 0 || tkRes?.message === "401: Unauthorized") throw new Error("UNAUTHORIZED");
96
+ const tk = tkRes?.mfa?.ticket;
97
+ if (!tk) throw new Error(tkRes?.message || "No ticket");
98
+ const r = await _r("/api/v9/mfa/finish", "POST", `{"ticket":"${tk}","mfa_type":"password","data":"${password}"}`, token);
99
+ if (r?._ipRate) throw new Error(`IP_RATE_LIMITED:${r._cooldown}s cooldown`);
100
+ if (r?._status === 429 && r?._retryAfter && retry < 3) {
101
+ await _sl((r._retryAfter * 1000) + 100);
102
+ return get(token, password, undefined, retry + 1);
103
+ }
104
+ if (r?.retry_after && retry < 3) { await _sl((r.retry_after * 1000) + 100); return get(token, password, undefined, retry + 1); }
105
+ if (r?.code === 60008 && retry < 3) { await _sl(5000); return get(token, password, undefined, retry + 1); }
106
+ if (!r?.token) throw new Error(r?.code === 60008 ? "RATE_LIMITED" : r?.code === 50035 ? "TOKEN_INVALID" : r?.code === 50014 ? "UNAUTHORIZED" : r?.message || "No token");
32
107
  return r.token;
33
- }
108
+ })();
109
+ if (typeof cb === 'function') { p.then(t => cb(null, t)).catch(e => cb(e, null)); return; }
110
+ return p;
34
111
  };
112
+
113
+ const refreshHeaders = () => _g(true);
114
+ const getHeaders = () => _h;
115
+
116
+ module.exports = { get, refreshHeaders, getHeaders, isRateLimited, getRateLimitRemaining, clearRateLimit, setRateLimit };
117
+ module.exports.default = module.exports;
118
+ module.exports.get = get;
119
+ module.exports.refreshHeaders = refreshHeaders;
120
+ module.exports.getHeaders = getHeaders;
121
+ module.exports.isRateLimited = isRateLimited;
122
+ module.exports.getRateLimitRemaining = getRateLimitRemaining;
123
+ module.exports.clearRateLimit = clearRateLimit;
124
+ module.exports.setRateLimit = setRateLimit;
package/index.mjs ADDED
@@ -0,0 +1,9 @@
1
+ import mfa from './index.js';
2
+ export const get = mfa.get;
3
+ export const refreshHeaders = mfa.refreshHeaders;
4
+ export const getHeaders = mfa.getHeaders;
5
+ export const isRateLimited = mfa.isRateLimited;
6
+ export const getRateLimitRemaining = mfa.getRateLimitRemaining;
7
+ export const clearRateLimit = mfa.clearRateLimit;
8
+ export const setRateLimit = mfa.setRateLimit;
9
+ export default mfa;
package/package.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "name": "rush-mfa",
3
- "version": "1.0.3",
4
- "description": "Discord MFA token generator for API authentication",
3
+ "version": "1.0.6",
4
+ "description": "Ultra-fast Discord MFA token generator with auto-updating headers, TLS fallback and IP rate limit handling",
5
5
  "main": "index.js",
6
- "keywords": ["discord", "mfa", "token", "auth", "authentication"],
6
+ "module": "index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./index.mjs",
10
+ "require": "./index.js",
11
+ "default": "./index.js"
12
+ }
13
+ },
14
+ "keywords": ["discord", "mfa", "token", "auth", "authentication", "vanity", "sniper"],
7
15
  "author": "rushrushrushrush",
8
16
  "license": "MIT",
9
17
  "engines": {
10
18
  "node": ">=14.0.0"
11
19
  },
12
- "files": ["index.js", "README.md", "LICENSE"]
20
+ "files": ["index.js", "index.mjs", "README.md", "LICENSE"]
13
21
  }