openlimiter 1.2.0 → 1.3.1

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 (55) hide show
  1. package/README.md +45 -16
  2. package/dist/bin.js +53 -4
  3. package/dist/bin.js.map +1 -1
  4. package/dist/cli.d.ts +110 -11
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +1064 -64
  7. package/dist/cli.js.map +1 -1
  8. package/dist/codex-device-login.d.ts +87 -0
  9. package/dist/codex-device-login.d.ts.map +1 -0
  10. package/dist/codex-device-login.js +291 -0
  11. package/dist/codex-device-login.js.map +1 -0
  12. package/dist/config.d.ts +44 -1
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +129 -7
  15. package/dist/config.js.map +1 -1
  16. package/dist/hub-auth.d.ts +137 -0
  17. package/dist/hub-auth.d.ts.map +1 -0
  18. package/dist/hub-auth.js +280 -0
  19. package/dist/hub-auth.js.map +1 -0
  20. package/dist/hub-sync.d.ts +144 -0
  21. package/dist/hub-sync.d.ts.map +1 -0
  22. package/dist/hub-sync.js +370 -0
  23. package/dist/hub-sync.js.map +1 -0
  24. package/dist/hub.d.ts +60 -0
  25. package/dist/hub.d.ts.map +1 -0
  26. package/dist/hub.js +178 -0
  27. package/dist/hub.js.map +1 -0
  28. package/dist/index.d.ts +6 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +6 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/ingest.d.ts +26 -0
  33. package/dist/ingest.d.ts.map +1 -1
  34. package/dist/ingest.js +116 -1
  35. package/dist/ingest.js.map +1 -1
  36. package/dist/qr.d.ts.map +1 -1
  37. package/dist/qr.js +1 -0
  38. package/dist/qr.js.map +1 -1
  39. package/dist/session.d.ts +46 -0
  40. package/dist/session.d.ts.map +1 -0
  41. package/dist/session.js +121 -0
  42. package/dist/session.js.map +1 -0
  43. package/dist/statusline.d.ts +14 -0
  44. package/dist/statusline.d.ts.map +1 -1
  45. package/dist/statusline.js +258 -19
  46. package/dist/statusline.js.map +1 -1
  47. package/dist/terminal.d.ts +109 -0
  48. package/dist/terminal.d.ts.map +1 -0
  49. package/dist/terminal.js +974 -0
  50. package/dist/terminal.js.map +1 -0
  51. package/package.json +4 -4
  52. package/dist/serve.d.ts +0 -170
  53. package/dist/serve.d.ts.map +0 -1
  54. package/dist/serve.js +0 -533
  55. package/dist/serve.js.map +0 -1
package/dist/serve.js DELETED
@@ -1,533 +0,0 @@
1
- import { randomBytes, timingSafeEqual } from "node:crypto";
2
- import { createServer } from "node:http";
3
- import { isIP } from "node:net";
4
- import { networkInterfaces } from "node:os";
5
- import { PROVIDER_CODES, buildAdvice, canonicalJson, floorFixed, readSnapshotCache } from "@openlimiter/core";
6
- import { encodeQr, renderQr } from "./qr.js";
7
- /**
8
- * The local quota server.
9
- *
10
- * This is the one place OpenLimiter opens a socket, and it is a listener, never
11
- * a client. It answers on your own network so a phone in your hand can read the
12
- * same numbers the statusline shows on the machine that is actually doing the
13
- * work. Nothing is uploaded, no account exists, and no provider is contacted.
14
- *
15
- * It is read only by construction. There is no route that writes, deletes, or
16
- * mutates anything, and the whole server never touches a credential store, a
17
- * provider authentication file, or a raw provider payload. The only thing it
18
- * can say is the bounded set of fields the agent context already carries:
19
- * provider codes, freshness states, a percentage, and a reset instant.
20
- *
21
- * Four gates stand between the socket and that payload:
22
- *
23
- * 1. A random token is generated on every start and is required on every
24
- * route that carries data. It lives only in memory, is never written to
25
- * disk, and is void the moment the command stops. The empty page shell at
26
- * the root is the one route served without it, because the token travels
27
- * in the URL fragment and a fragment never reaches a server. The shell
28
- * holds no quota state, no token and no account detail: it is markup that
29
- * then has to authenticate before anything is filled in.
30
- * 2. The Host header must be an address literal or localhost, which is what
31
- * closes a DNS rebinding attack from a page on an attacker's domain.
32
- * 3. Cross origin requests are refused outright. The page this server hands
33
- * out is same origin, so nothing legitimate needs a relaxation.
34
- * 4. Only GET and HEAD are answered. Every other method is refused before
35
- * the token is even looked at.
36
- *
37
- * It is meant for a trusted home or office network. It is not exposed to the
38
- * internet, it opens no ports on a router, and it should not be reachable from
39
- * one. Anyone already on your network who also has the token can read your
40
- * quota state, which is the whole point and also the whole risk.
41
- */
42
- /** Default listening port. Unassigned by IANA, and easy to type. */
43
- export const DEFAULT_SERVE_PORT = 7317;
44
- /** Default binding. Every interface, so a phone on the same network reaches it. */
45
- export const DEFAULT_SERVE_HOST = "0.0.0.0";
46
- /** Payload schema version. Bumped only on a breaking shape change. */
47
- export const SERVE_SCHEMA = 1;
48
- /**
49
- * Name the token travels under. Short, because it goes in a QR symbol.
50
- *
51
- * It is the key in the URL FRAGMENT of every address this command prints, and
52
- * for one more release it is also accepted as a query parameter so an address
53
- * saved from an older build keeps working.
54
- */
55
- export const TOKEN_PARAMETER = "t";
56
- /**
57
- * Why the token is in the fragment rather than the query.
58
- *
59
- * A query string is part of the request line. It lands in the phone's browser
60
- * history, in the omnibox suggestions, in whatever cloud that browser syncs
61
- * history to, and in the Referer of any link the page might carry. A fragment
62
- * is never sent to a server at all: it stays in the address bar until the page
63
- * itself reads it, and the page's first act is to move it into sessionStorage
64
- * and rewrite the address to a clean one. From that point the token exists only
65
- * in the tab's own memory and is sent as an Authorization header, so no URL
66
- * anywhere on the phone carries the capability.
67
- *
68
- * A pairing link is a credential in a URL, so it gets the same treatment as one.
69
- */
70
- export const TOKEN_FRAGMENT_PREFIX = "#" + TOKEN_PARAMETER + "=";
71
- /** Key the page keeps the token under, inside its own tab and nowhere else. */
72
- const TOKEN_STORAGE_KEY = "openlimiter.token";
73
- /** Longest request target this server will look at. */
74
- const MAX_TARGET_LENGTH = 512;
75
- /** Bounds on the socket, so a stalled client cannot hold a slot forever. */
76
- const HEADERS_TIMEOUT_MILLISECONDS = 5_000;
77
- const REQUEST_TIMEOUT_MILLISECONDS = 10_000;
78
- const KEEP_ALIVE_TIMEOUT_MILLISECONDS = 5_000;
79
- const MAX_CONNECTIONS = 32;
80
- /** Bytes of entropy in the token. */
81
- const TOKEN_BYTES = 16;
82
- /**
83
- * The address another device on the same network should open.
84
- *
85
- * A bound address of every interface tells a phone nothing, so the first non
86
- * internal IPv4 address is what gets printed. IPv6 is skipped deliberately: a
87
- * link local address needs a zone identifier that does not survive a QR symbol.
88
- */
89
- export function lanAddress() {
90
- for (const entries of Object.values(networkInterfaces())) {
91
- for (const entry of entries ?? []) {
92
- if (entry.family === "IPv4" && !entry.internal)
93
- return entry.address;
94
- }
95
- }
96
- return null;
97
- }
98
- function authority(host, port) {
99
- const wrapped = isIP(host) === 6 ? "[" + host + "]" : host;
100
- return wrapped + ":" + String(port);
101
- }
102
- /**
103
- * Decide whether the Host header may be served.
104
- *
105
- * A DNS rebinding attack works by pointing a name the attacker controls at an
106
- * address on your network and letting a page on that name talk to a server
107
- * there. The name always arrives in the Host header, so refusing every name
108
- * except localhost closes it. Address literals are accepted because they
109
- * cannot be rebound.
110
- */
111
- export function isAllowedHostHeader(header, boundPort) {
112
- if (header === undefined || header === "")
113
- return false;
114
- let parsed;
115
- try {
116
- parsed = new URL("http://" + header);
117
- }
118
- catch {
119
- return false;
120
- }
121
- if (parsed.pathname !== "/" || parsed.search !== "" || parsed.username !== "") {
122
- return false;
123
- }
124
- const port = parsed.port === "" ? 80 : Number(parsed.port);
125
- if (port !== boundPort)
126
- return false;
127
- const hostname = parsed.hostname;
128
- if (hostname === "localhost")
129
- return true;
130
- /* A bracketed IPv6 literal loses its brackets through the URL parser. */
131
- const literal = hostname.startsWith("[") ? hostname.slice(1, -1) : hostname;
132
- return isIP(literal) !== 0;
133
- }
134
- /**
135
- * Read the token a request presents.
136
- *
137
- * The header is the supported way and is read first. The query parameter is a
138
- * fallback kept for one release so an address copied out of an older build
139
- * still answers; nothing this command prints uses it any more.
140
- */
141
- export function presentedToken(authorization, target) {
142
- if (typeof authorization === "string") {
143
- const space = authorization.indexOf(" ");
144
- if (space > 0 && authorization.slice(0, space).toLowerCase() === "bearer") {
145
- const value = authorization.slice(space + 1).trim();
146
- if (value !== "")
147
- return value;
148
- }
149
- return null;
150
- }
151
- return target.searchParams.get(TOKEN_PARAMETER);
152
- }
153
- function constantTimeEquals(left, right) {
154
- const a = Buffer.from(left, "utf8");
155
- const b = Buffer.from(right, "utf8");
156
- if (a.length !== b.length) {
157
- /* Compare against itself so the work done does not leak the length. */
158
- timingSafeEqual(a, a);
159
- return false;
160
- }
161
- return timingSafeEqual(a, b);
162
- }
163
- /**
164
- * Build the document served on the quota route.
165
- *
166
- * The advice engine in the core does the work, exactly as it does for the
167
- * statusline and the hook, so a phone can never see a number the agent would
168
- * not have seen. A cache that cannot be read becomes an empty document with
169
- * every provider unknown, never a zero and never a cap.
170
- */
171
- export async function quotaDocument(stateDirectory, now) {
172
- const cached = await readSnapshotCache(stateDirectory);
173
- const advice = buildAdvice(cached.ok ? cached.snapshots : [], now, PROVIDER_CODES);
174
- return {
175
- schema: SERVE_SCHEMA,
176
- generatedAt: now,
177
- reason: advice.reason,
178
- providers: advice.providers.map((entry) => ({
179
- provider: entry.provider,
180
- state: entry.state,
181
- usagePercent: Number(floorFixed(entry.usagePercent, 2)),
182
- resetAt: entry.resetAt
183
- })),
184
- unknown: [...advice.unknownProviders]
185
- };
186
- }
187
- function securityHeaders() {
188
- return {
189
- "cache-control": "no-store",
190
- "referrer-policy": "no-referrer",
191
- "x-content-type-options": "nosniff",
192
- "x-frame-options": "DENY",
193
- /* No Access-Control-Allow-Origin is ever sent. The page is same origin. */
194
- vary: "Origin"
195
- };
196
- }
197
- function sendJson(request, response, status, body) {
198
- const text = canonicalJson(body);
199
- response.writeHead(status, {
200
- ...securityHeaders(),
201
- "content-security-policy": "default-src 'none'",
202
- "content-type": "application/json; charset=utf-8",
203
- "content-length": String(Buffer.byteLength(text, "utf8"))
204
- });
205
- /* A HEAD response carries the headers of the GET and none of its body. */
206
- response.end(request.method === "HEAD" ? undefined : text);
207
- }
208
- function sendHtml(request, response, status, html) {
209
- response.writeHead(status, {
210
- ...securityHeaders(),
211
- "content-security-policy": [
212
- "default-src 'none'",
213
- "style-src 'unsafe-inline'",
214
- "script-src 'unsafe-inline'",
215
- "connect-src 'self'",
216
- "base-uri 'none'",
217
- "form-action 'none'"
218
- ].join("; "),
219
- "content-type": "text/html; charset=utf-8",
220
- "content-length": String(Buffer.byteLength(html, "utf8"))
221
- });
222
- response.end(request.method === "HEAD" ? undefined : html);
223
- }
224
- /**
225
- * The mobile page.
226
- *
227
- * One file, no build step, no external request of any kind. It reads the same
228
- * JSON route a script would, on the same origin, carrying the same token that
229
- * is already in the address bar. The colours mirror the design tokens on the
230
- * site, and the whole thing follows the reader's own light or dark setting.
231
- */
232
- export function mobilePage() {
233
- return `<!doctype html>
234
- <html lang="en">
235
- <head>
236
- <meta charset="utf-8">
237
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
238
- <meta name="robots" content="noindex, nofollow">
239
- <meta name="color-scheme" content="light dark">
240
- <title>OpenLimiter quota</title>
241
- <style>
242
- :root{color-scheme:light dark;--canvas:#fff;--surface:#f6f6f7;--track:#e2e2e6;--hairline:#e5e5e8;--heading:#0d0d0f;--body:#53535c;--muted:#62626c;--accent:#0866ff;--warn:#b45309;--stop:#b42318}
243
- @media (prefers-color-scheme:dark){:root{--canvas:#0a0a0b;--surface:#121214;--track:#26262b;--hairline:#232327;--heading:#f2f2f3;--body:#adadb6;--muted:#94949e;--accent:#6ba6ff;--warn:#f0b429;--stop:#ff6b6b}}
244
- *{box-sizing:border-box}
245
- body{margin:0;padding:20px 16px 48px;background:var(--canvas);color:var(--body);font:15px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}
246
- main{max-width:520px;margin:0 auto}
247
- h1{margin:0 0 2px;font-size:19px;font-weight:600;letter-spacing:-.01em;color:var(--heading)}
248
- .sub{margin:0 0 20px;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted)}
249
- .state{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--hairline);border-radius:8px;padding:5px 10px;font:11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);background:var(--surface)}
250
- .dot{width:7px;height:7px;border-radius:50%;background:var(--accent)}
251
- .card{border:1px solid var(--hairline);border-radius:14px;background:var(--surface);padding:14px 16px;margin-top:10px}
252
- .row{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
253
- .name{font-size:14px;font-weight:600;color:var(--heading);letter-spacing:.01em}
254
- .pct{font:600 20px/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--heading);font-variant-numeric:tabular-nums}
255
- .bar{margin-top:10px;height:6px;border-radius:999px;background:var(--track);overflow:hidden}
256
- .fill{height:100%;border-radius:999px;background:var(--accent);transition:width .3s ease}
257
- .fill.near{background:var(--warn)}
258
- .fill.at{background:var(--stop)}
259
- .meta{margin-top:8px;display:flex;gap:10px;flex-wrap:wrap;font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted)}
260
- .note{margin-top:22px;font-size:12px;line-height:1.6;color:var(--muted)}
261
- .err{color:var(--stop)}
262
- </style>
263
- </head>
264
- <body>
265
- <main>
266
- <h1>OpenLimiter</h1>
267
- <p class="sub" id="stamp">Loading quota from this machine.</p>
268
- <p><span class="state"><span class="dot" id="dot"></span><span id="reason">reading</span></span></p>
269
- <div id="cards"></div>
270
- <p class="note" id="unknown"></p>
271
- <p class="note">
272
- Served from the machine running OpenLimiter, over your own network. Nothing here
273
- left that machine, nothing was uploaded, and this page can only read.
274
- </p>
275
- </main>
276
- <script>
277
- (function(){
278
- /* The token arrives in the fragment, which no server ever sees. Move it into
279
- this tab's own memory and wipe the address, so the history entry the phone
280
- keeps, and syncs, carries no capability.
281
-
282
- The query is read as well, and first, because an address printed by an
283
- older build put the token there. Such an address still reaches this page,
284
- and a page that only looked at the fragment would sit at 401 forever with
285
- the token visible in the address bar the whole time. Reading it, then
286
- scrubbing BOTH the query and the fragment in one replaceState, turns the
287
- old address into the new behaviour on its first load. */
288
- var key = ${JSON.stringify(TOKEN_STORAGE_KEY)};
289
- var name = ${JSON.stringify(TOKEN_PARAMETER)};
290
- var prefix = ${JSON.stringify(TOKEN_FRAGMENT_PREFIX)};
291
- var token = "";
292
- var fromAddress = false;
293
- try {
294
- var queried = new URLSearchParams(location.search).get(name);
295
- if (queried) {
296
- token = queried;
297
- fromAddress = true;
298
- } else if (location.hash.indexOf(prefix) === 0) {
299
- token = decodeURIComponent(location.hash.slice(prefix.length));
300
- fromAddress = true;
301
- }
302
- } catch (error) {
303
- token = "";
304
- }
305
- if (fromAddress) {
306
- try { sessionStorage.setItem(key, token); } catch (error) {}
307
- /* One rewrite, dropping the query and the fragment together. */
308
- try { history.replaceState(null, "", location.pathname); } catch (error) {}
309
- } else {
310
- try { token = sessionStorage.getItem(key) || ""; } catch (error) { token = ""; }
311
- }
312
- var cards = document.getElementById("cards");
313
- var stamp = document.getElementById("stamp");
314
- var reason = document.getElementById("reason");
315
- var dot = document.getElementById("dot");
316
- var unknown = document.getElementById("unknown");
317
- function tone(value){ return value >= 100 ? "at" : value >= 80 ? "near" : ""; }
318
- function clock(value){
319
- if (value === null) return "no reset";
320
- var when = new Date(value);
321
- return isNaN(when.getTime()) ? "no reset" : "resets " + when.toLocaleString();
322
- }
323
- function draw(document_){
324
- reason.textContent = document_.reason.toLowerCase().replace("_"," ");
325
- dot.style.background = document_.reason === "AT_CAP" ? "var(--stop)"
326
- : document_.reason === "NEAR_CAP" ? "var(--warn)" : "var(--accent)";
327
- stamp.textContent = "Updated " + new Date(document_.generatedAt).toLocaleTimeString();
328
- stamp.className = "sub";
329
- cards.textContent = "";
330
- document_.providers.forEach(function(entry){
331
- var card = document.createElement("div");
332
- card.className = "card";
333
- var row = document.createElement("div");
334
- row.className = "row";
335
- var name = document.createElement("span");
336
- name.className = "name";
337
- name.textContent = entry.provider;
338
- var pct = document.createElement("span");
339
- pct.className = "pct";
340
- pct.textContent = entry.usagePercent.toFixed(1) + "%";
341
- row.appendChild(name); row.appendChild(pct);
342
- var bar = document.createElement("div");
343
- bar.className = "bar";
344
- var fill = document.createElement("div");
345
- fill.className = "fill " + tone(entry.usagePercent);
346
- fill.style.width = Math.min(100, Math.max(0, entry.usagePercent)) + "%";
347
- bar.appendChild(fill);
348
- var meta = document.createElement("div");
349
- meta.className = "meta";
350
- var state = document.createElement("span");
351
- state.textContent = entry.state;
352
- var reset = document.createElement("span");
353
- reset.textContent = clock(entry.resetAt);
354
- meta.appendChild(state); meta.appendChild(reset);
355
- card.appendChild(row); card.appendChild(bar); card.appendChild(meta);
356
- cards.appendChild(card);
357
- });
358
- unknown.textContent = document_.unknown.length === 0
359
- ? ""
360
- : "Unknown, so nothing is claimed for them: " + document_.unknown.join(", ") + ".";
361
- }
362
- function load(){
363
- /* The token goes in a header, never in a URL, so nothing that repeats every
364
- fifteen seconds can write it into a log or a history entry. */
365
- fetch("/quota.json", {
366
- cache: "no-store",
367
- headers: { "authorization": "Bearer " + token }
368
- })
369
- .then(function(response){
370
- if (!response.ok) throw new Error(String(response.status));
371
- return response.json();
372
- })
373
- .then(draw)
374
- .catch(function(error){
375
- stamp.className = "sub err";
376
- stamp.textContent = error.message === "401"
377
- ? "The token in this address is not valid. Scan the code again."
378
- : "Cannot reach the machine running OpenLimiter.";
379
- });
380
- }
381
- load();
382
- setInterval(load, 15000);
383
- })();
384
- </script>
385
- </body>
386
- </html>
387
- `;
388
- }
389
- /**
390
- * Start the local quota server.
391
- *
392
- * The returned handle carries the port that was actually bound, which matters
393
- * when port zero was asked for, and closes every connection on shutdown so a
394
- * caller is never left waiting on a keep alive socket.
395
- */
396
- export async function startQuotaServer(options = {}) {
397
- const host = options.host ?? DEFAULT_SERVE_HOST;
398
- const port = options.port ?? DEFAULT_SERVE_PORT;
399
- const token = options.token ?? randomBytes(TOKEN_BYTES).toString("base64url");
400
- const clock = options.now ?? (() => new Date().toISOString());
401
- const stateDirectory = options.stateDirectory;
402
- /* Rewritten once the socket is bound, which is what port zero needs. */
403
- let listeningPort = port;
404
- async function handle(request, response) {
405
- const method = request.method ?? "GET";
406
- if (method !== "GET" && method !== "HEAD") {
407
- response.writeHead(405, { ...securityHeaders(), allow: "GET, HEAD" });
408
- response.end();
409
- return;
410
- }
411
- const rawTarget = request.url ?? "/";
412
- if (rawTarget.length > MAX_TARGET_LENGTH) {
413
- sendJson(request, response, 414, { error: "target_too_long" });
414
- return;
415
- }
416
- if (!isAllowedHostHeader(request.headers.host, listeningPort)) {
417
- sendJson(request, response, 403, { error: "host_not_allowed" });
418
- return;
419
- }
420
- /* No cross origin caller is ever wanted, so any origin at all is refused. */
421
- if (typeof request.headers.origin === "string") {
422
- sendJson(request, response, 403, { error: "cross_origin_refused" });
423
- return;
424
- }
425
- let target;
426
- try {
427
- target = new URL(rawTarget, "http://localhost");
428
- }
429
- catch {
430
- sendJson(request, response, 400, { error: "bad_request" });
431
- return;
432
- }
433
- /*
434
- * The shell is served before the token is looked at, and only the shell.
435
- * It has to be, because the token is in the fragment and the fragment never
436
- * arrives here. It carries no reading, no token and no account detail, so
437
- * anyone who fetches it learns only that this command is running, which the
438
- * open port already told them.
439
- */
440
- if (target.pathname === "/") {
441
- sendHtml(request, response, 200, mobilePage());
442
- return;
443
- }
444
- const presented = presentedToken(request.headers.authorization, target);
445
- if (presented === null || !constantTimeEquals(presented, token)) {
446
- sendJson(request, response, 401, { error: "token_required" });
447
- return;
448
- }
449
- if (target.pathname === "/quota.json") {
450
- sendJson(request, response, 200, await quotaDocument(stateDirectory, clock()));
451
- return;
452
- }
453
- sendJson(request, response, 404, { error: "not_found" });
454
- }
455
- const server = createServer((request, response) => {
456
- /* Nothing here consumes a body. Draining keeps the socket reusable. */
457
- request.resume();
458
- handle(request, response).catch(() => {
459
- if (!response.headersSent) {
460
- sendJson(request, response, 500, { error: "internal" });
461
- return;
462
- }
463
- response.end();
464
- });
465
- });
466
- server.headersTimeout = HEADERS_TIMEOUT_MILLISECONDS;
467
- server.requestTimeout = REQUEST_TIMEOUT_MILLISECONDS;
468
- server.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MILLISECONDS;
469
- server.maxConnections = MAX_CONNECTIONS;
470
- await new Promise((resolve, reject) => {
471
- const onError = (error) => {
472
- server.off("listening", onListening);
473
- reject(error instanceof Error ? error : new Error("listen failed"));
474
- };
475
- const onListening = () => {
476
- server.off("error", onError);
477
- const address = server.address();
478
- if (address !== null && typeof address === "object") {
479
- listeningPort = address.port;
480
- }
481
- resolve();
482
- };
483
- server.once("error", onError);
484
- server.once("listening", onListening);
485
- server.listen(port, host);
486
- });
487
- /* Fragment, not query. See TOKEN_FRAGMENT_PREFIX above for why. */
488
- const suffix = "/" + TOKEN_FRAGMENT_PREFIX + token;
489
- const advertised = host === "0.0.0.0" || host === "::"
490
- ? lanAddress() ?? "127.0.0.1"
491
- : host;
492
- return {
493
- host,
494
- port: listeningPort,
495
- token,
496
- url: "http://" + authority(advertised, listeningPort) + suffix,
497
- localUrl: "http://" + authority("127.0.0.1", listeningPort) + suffix,
498
- close: async () => {
499
- await new Promise((resolve) => {
500
- server.close(() => {
501
- resolve();
502
- });
503
- server.closeAllConnections();
504
- });
505
- }
506
- };
507
- }
508
- /**
509
- * The text printed when the server starts.
510
- *
511
- * The address is printed before the symbol and again after it, because a
512
- * terminal that cannot draw the symbol still has to be usable, and because the
513
- * address is the thing a person types when a camera will not focus.
514
- */
515
- export function serveBanner(handle, options = {}) {
516
- const lines = [
517
- "OpenLimiter is serving read only quota on your local network.",
518
- "",
519
- " " + handle.url,
520
- ""
521
- ];
522
- if (options.withoutQr !== true) {
523
- try {
524
- lines.push(renderQr(encodeQr(handle.url), { color: options.color ?? false }), "");
525
- }
526
- catch {
527
- lines.push("The address is too long to draw as a scannable code.", "");
528
- }
529
- }
530
- lines.push("On this machine: " + handle.localUrl, "", "Scan the code with a phone on the same network, or type the address.", "The token changes every time this command starts, and nothing answers without it.", "The code and the address ARE the key: anyone who can see this screen, or a", "photograph of it, can read your quota for as long as this command runs.", "Read only: no route here can change anything on this machine, and no", "credential, provider payload, or account detail is ever served.", "Meant for a trusted home or office network. This listens on every interface", "on this machine, so do not port forward this port and do not open it on a", "firewall: nothing but your router's default is keeping it off the internet.", "", "Press Ctrl C to stop.");
531
- return lines.join("\n");
532
- }
533
- //# sourceMappingURL=serve.js.map
package/dist/serve.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}