@rubytech/create-maxy-code 0.1.5 → 0.1.7

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.
@@ -1,4148 +0,0 @@
1
- import {
2
- getConversationOwner
3
- } from "./chunk-GPUCA2RQ.js";
4
-
5
- // node_modules/hono/dist/compose.js
6
- var compose = (middleware, onError, onNotFound) => {
7
- return (context, next) => {
8
- let index = -1;
9
- return dispatch(0);
10
- async function dispatch(i) {
11
- if (i <= index) {
12
- throw new Error("next() called multiple times");
13
- }
14
- index = i;
15
- let res;
16
- let isError = false;
17
- let handler;
18
- if (middleware[i]) {
19
- handler = middleware[i][0][0];
20
- context.req.routeIndex = i;
21
- } else {
22
- handler = i === middleware.length && next || void 0;
23
- }
24
- if (handler) {
25
- try {
26
- res = await handler(context, () => dispatch(i + 1));
27
- } catch (err) {
28
- if (err instanceof Error && onError) {
29
- context.error = err;
30
- res = await onError(err, context);
31
- isError = true;
32
- } else {
33
- throw err;
34
- }
35
- }
36
- } else {
37
- if (context.finalized === false && onNotFound) {
38
- res = await onNotFound(context);
39
- }
40
- }
41
- if (res && (context.finalized === false || isError)) {
42
- context.res = res;
43
- }
44
- return context;
45
- }
46
- };
47
- };
48
-
49
- // node_modules/hono/dist/request/constants.js
50
- var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
51
-
52
- // node_modules/hono/dist/utils/body.js
53
- var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
54
- const { all = false, dot = false } = options;
55
- const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
56
- const contentType = headers.get("Content-Type");
57
- if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
58
- return parseFormData(request, { all, dot });
59
- }
60
- return {};
61
- };
62
- async function parseFormData(request, options) {
63
- const formData = await request.formData();
64
- if (formData) {
65
- return convertFormDataToBodyData(formData, options);
66
- }
67
- return {};
68
- }
69
- function convertFormDataToBodyData(formData, options) {
70
- const form = /* @__PURE__ */ Object.create(null);
71
- formData.forEach((value, key) => {
72
- const shouldParseAllValues = options.all || key.endsWith("[]");
73
- if (!shouldParseAllValues) {
74
- form[key] = value;
75
- } else {
76
- handleParsingAllValues(form, key, value);
77
- }
78
- });
79
- if (options.dot) {
80
- Object.entries(form).forEach(([key, value]) => {
81
- const shouldParseDotValues = key.includes(".");
82
- if (shouldParseDotValues) {
83
- handleParsingNestedValues(form, key, value);
84
- delete form[key];
85
- }
86
- });
87
- }
88
- return form;
89
- }
90
- var handleParsingAllValues = (form, key, value) => {
91
- if (form[key] !== void 0) {
92
- if (Array.isArray(form[key])) {
93
- ;
94
- form[key].push(value);
95
- } else {
96
- form[key] = [form[key], value];
97
- }
98
- } else {
99
- if (!key.endsWith("[]")) {
100
- form[key] = value;
101
- } else {
102
- form[key] = [value];
103
- }
104
- }
105
- };
106
- var handleParsingNestedValues = (form, key, value) => {
107
- if (/(?:^|\.)__proto__\./.test(key)) {
108
- return;
109
- }
110
- let nestedForm = form;
111
- const keys = key.split(".");
112
- keys.forEach((key2, index) => {
113
- if (index === keys.length - 1) {
114
- nestedForm[key2] = value;
115
- } else {
116
- if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
117
- nestedForm[key2] = /* @__PURE__ */ Object.create(null);
118
- }
119
- nestedForm = nestedForm[key2];
120
- }
121
- });
122
- };
123
-
124
- // node_modules/hono/dist/utils/url.js
125
- var splitPath = (path) => {
126
- const paths = path.split("/");
127
- if (paths[0] === "") {
128
- paths.shift();
129
- }
130
- return paths;
131
- };
132
- var splitRoutingPath = (routePath) => {
133
- const { groups, path } = extractGroupsFromPath(routePath);
134
- const paths = splitPath(path);
135
- return replaceGroupMarks(paths, groups);
136
- };
137
- var extractGroupsFromPath = (path) => {
138
- const groups = [];
139
- path = path.replace(/\{[^}]+\}/g, (match2, index) => {
140
- const mark = `@${index}`;
141
- groups.push([mark, match2]);
142
- return mark;
143
- });
144
- return { groups, path };
145
- };
146
- var replaceGroupMarks = (paths, groups) => {
147
- for (let i = groups.length - 1; i >= 0; i--) {
148
- const [mark] = groups[i];
149
- for (let j = paths.length - 1; j >= 0; j--) {
150
- if (paths[j].includes(mark)) {
151
- paths[j] = paths[j].replace(mark, groups[i][1]);
152
- break;
153
- }
154
- }
155
- }
156
- return paths;
157
- };
158
- var patternCache = {};
159
- var getPattern = (label, next) => {
160
- if (label === "*") {
161
- return "*";
162
- }
163
- const match2 = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
164
- if (match2) {
165
- const cacheKey2 = `${label}#${next}`;
166
- if (!patternCache[cacheKey2]) {
167
- if (match2[2]) {
168
- patternCache[cacheKey2] = next && next[0] !== ":" && next[0] !== "*" ? [cacheKey2, match2[1], new RegExp(`^${match2[2]}(?=/${next})`)] : [label, match2[1], new RegExp(`^${match2[2]}$`)];
169
- } else {
170
- patternCache[cacheKey2] = [label, match2[1], true];
171
- }
172
- }
173
- return patternCache[cacheKey2];
174
- }
175
- return null;
176
- };
177
- var tryDecode = (str, decoder) => {
178
- try {
179
- return decoder(str);
180
- } catch {
181
- return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
182
- try {
183
- return decoder(match2);
184
- } catch {
185
- return match2;
186
- }
187
- });
188
- }
189
- };
190
- var tryDecodeURI = (str) => tryDecode(str, decodeURI);
191
- var getPath = (request) => {
192
- const url = request.url;
193
- const start = url.indexOf("/", url.indexOf(":") + 4);
194
- let i = start;
195
- for (; i < url.length; i++) {
196
- const charCode = url.charCodeAt(i);
197
- if (charCode === 37) {
198
- const queryIndex = url.indexOf("?", i);
199
- const hashIndex = url.indexOf("#", i);
200
- const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
201
- const path = url.slice(start, end);
202
- return tryDecodeURI(path.includes("%25") ? path.replace(/%25/g, "%2525") : path);
203
- } else if (charCode === 63 || charCode === 35) {
204
- break;
205
- }
206
- }
207
- return url.slice(start, i);
208
- };
209
- var getPathNoStrict = (request) => {
210
- const result = getPath(request);
211
- return result.length > 1 && result.at(-1) === "/" ? result.slice(0, -1) : result;
212
- };
213
- var mergePath = (base, sub, ...rest) => {
214
- if (rest.length) {
215
- sub = mergePath(sub, ...rest);
216
- }
217
- return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
218
- };
219
- var checkOptionalParameter = (path) => {
220
- if (path.charCodeAt(path.length - 1) !== 63 || !path.includes(":")) {
221
- return null;
222
- }
223
- const segments = path.split("/");
224
- const results = [];
225
- let basePath = "";
226
- segments.forEach((segment) => {
227
- if (segment !== "" && !/\:/.test(segment)) {
228
- basePath += "/" + segment;
229
- } else if (/\:/.test(segment)) {
230
- if (/\?/.test(segment)) {
231
- if (results.length === 0 && basePath === "") {
232
- results.push("/");
233
- } else {
234
- results.push(basePath);
235
- }
236
- const optionalSegment = segment.replace("?", "");
237
- basePath += "/" + optionalSegment;
238
- results.push(basePath);
239
- } else {
240
- basePath += "/" + segment;
241
- }
242
- }
243
- });
244
- return results.filter((v, i, a) => a.indexOf(v) === i);
245
- };
246
- var _decodeURI = (value) => {
247
- if (!/[%+]/.test(value)) {
248
- return value;
249
- }
250
- if (value.indexOf("+") !== -1) {
251
- value = value.replace(/\+/g, " ");
252
- }
253
- return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
254
- };
255
- var _getQueryParam = (url, key, multiple) => {
256
- let encoded;
257
- if (!multiple && key && !/[%+]/.test(key)) {
258
- let keyIndex2 = url.indexOf("?", 8);
259
- if (keyIndex2 === -1) {
260
- return void 0;
261
- }
262
- if (!url.startsWith(key, keyIndex2 + 1)) {
263
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
264
- }
265
- while (keyIndex2 !== -1) {
266
- const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
267
- if (trailingKeyCode === 61) {
268
- const valueIndex = keyIndex2 + key.length + 2;
269
- const endIndex = url.indexOf("&", valueIndex);
270
- return _decodeURI(url.slice(valueIndex, endIndex === -1 ? void 0 : endIndex));
271
- } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
272
- return "";
273
- }
274
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
275
- }
276
- encoded = /[%+]/.test(url);
277
- if (!encoded) {
278
- return void 0;
279
- }
280
- }
281
- const results = {};
282
- encoded ??= /[%+]/.test(url);
283
- let keyIndex = url.indexOf("?", 8);
284
- while (keyIndex !== -1) {
285
- const nextKeyIndex = url.indexOf("&", keyIndex + 1);
286
- let valueIndex = url.indexOf("=", keyIndex);
287
- if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
288
- valueIndex = -1;
289
- }
290
- let name = url.slice(
291
- keyIndex + 1,
292
- valueIndex === -1 ? nextKeyIndex === -1 ? void 0 : nextKeyIndex : valueIndex
293
- );
294
- if (encoded) {
295
- name = _decodeURI(name);
296
- }
297
- keyIndex = nextKeyIndex;
298
- if (name === "") {
299
- continue;
300
- }
301
- let value;
302
- if (valueIndex === -1) {
303
- value = "";
304
- } else {
305
- value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? void 0 : nextKeyIndex);
306
- if (encoded) {
307
- value = _decodeURI(value);
308
- }
309
- }
310
- if (multiple) {
311
- if (!(results[name] && Array.isArray(results[name]))) {
312
- results[name] = [];
313
- }
314
- ;
315
- results[name].push(value);
316
- } else {
317
- results[name] ??= value;
318
- }
319
- }
320
- return key ? results[key] : results;
321
- };
322
- var getQueryParam = _getQueryParam;
323
- var getQueryParams = (url, key) => {
324
- return _getQueryParam(url, key, true);
325
- };
326
- var decodeURIComponent_ = decodeURIComponent;
327
-
328
- // node_modules/hono/dist/request.js
329
- var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
330
- var HonoRequest = class {
331
- /**
332
- * `.raw` can get the raw Request object.
333
- *
334
- * @see {@link https://hono.dev/docs/api/request#raw}
335
- *
336
- * @example
337
- * ```ts
338
- * // For Cloudflare Workers
339
- * app.post('/', async (c) => {
340
- * const metadata = c.req.raw.cf?.hostMetadata?
341
- * ...
342
- * })
343
- * ```
344
- */
345
- raw;
346
- #validatedData;
347
- // Short name of validatedData
348
- #matchResult;
349
- routeIndex = 0;
350
- /**
351
- * `.path` can get the pathname of the request.
352
- *
353
- * @see {@link https://hono.dev/docs/api/request#path}
354
- *
355
- * @example
356
- * ```ts
357
- * app.get('/about/me', (c) => {
358
- * const pathname = c.req.path // `/about/me`
359
- * })
360
- * ```
361
- */
362
- path;
363
- bodyCache = {};
364
- constructor(request, path = "/", matchResult = [[]]) {
365
- this.raw = request;
366
- this.path = path;
367
- this.#matchResult = matchResult;
368
- this.#validatedData = {};
369
- }
370
- param(key) {
371
- return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
372
- }
373
- #getDecodedParam(key) {
374
- const paramKey = this.#matchResult[0][this.routeIndex][1][key];
375
- const param = this.#getParamValue(paramKey);
376
- return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
377
- }
378
- #getAllDecodedParams() {
379
- const decoded = {};
380
- const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
381
- for (const key of keys) {
382
- const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
383
- if (value !== void 0) {
384
- decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
385
- }
386
- }
387
- return decoded;
388
- }
389
- #getParamValue(paramKey) {
390
- return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
391
- }
392
- query(key) {
393
- return getQueryParam(this.url, key);
394
- }
395
- queries(key) {
396
- return getQueryParams(this.url, key);
397
- }
398
- header(name) {
399
- if (name) {
400
- return this.raw.headers.get(name) ?? void 0;
401
- }
402
- const headerData = {};
403
- this.raw.headers.forEach((value, key) => {
404
- headerData[key] = value;
405
- });
406
- return headerData;
407
- }
408
- async parseBody(options) {
409
- return this.bodyCache.parsedBody ??= await parseBody(this, options);
410
- }
411
- #cachedBody = (key) => {
412
- const { bodyCache, raw: raw2 } = this;
413
- const cachedBody = bodyCache[key];
414
- if (cachedBody) {
415
- return cachedBody;
416
- }
417
- const anyCachedKey = Object.keys(bodyCache)[0];
418
- if (anyCachedKey) {
419
- return bodyCache[anyCachedKey].then((body) => {
420
- if (anyCachedKey === "json") {
421
- body = JSON.stringify(body);
422
- }
423
- return new Response(body)[key]();
424
- });
425
- }
426
- return bodyCache[key] = raw2[key]();
427
- };
428
- /**
429
- * `.json()` can parse Request body of type `application/json`
430
- *
431
- * @see {@link https://hono.dev/docs/api/request#json}
432
- *
433
- * @example
434
- * ```ts
435
- * app.post('/entry', async (c) => {
436
- * const body = await c.req.json()
437
- * })
438
- * ```
439
- */
440
- json() {
441
- return this.#cachedBody("text").then((text) => JSON.parse(text));
442
- }
443
- /**
444
- * `.text()` can parse Request body of type `text/plain`
445
- *
446
- * @see {@link https://hono.dev/docs/api/request#text}
447
- *
448
- * @example
449
- * ```ts
450
- * app.post('/entry', async (c) => {
451
- * const body = await c.req.text()
452
- * })
453
- * ```
454
- */
455
- text() {
456
- return this.#cachedBody("text");
457
- }
458
- /**
459
- * `.arrayBuffer()` parse Request body as an `ArrayBuffer`
460
- *
461
- * @see {@link https://hono.dev/docs/api/request#arraybuffer}
462
- *
463
- * @example
464
- * ```ts
465
- * app.post('/entry', async (c) => {
466
- * const body = await c.req.arrayBuffer()
467
- * })
468
- * ```
469
- */
470
- arrayBuffer() {
471
- return this.#cachedBody("arrayBuffer");
472
- }
473
- /**
474
- * Parses the request body as a `Blob`.
475
- * @example
476
- * ```ts
477
- * app.post('/entry', async (c) => {
478
- * const body = await c.req.blob();
479
- * });
480
- * ```
481
- * @see https://hono.dev/docs/api/request#blob
482
- */
483
- blob() {
484
- return this.#cachedBody("blob");
485
- }
486
- /**
487
- * Parses the request body as `FormData`.
488
- * @example
489
- * ```ts
490
- * app.post('/entry', async (c) => {
491
- * const body = await c.req.formData();
492
- * });
493
- * ```
494
- * @see https://hono.dev/docs/api/request#formdata
495
- */
496
- formData() {
497
- return this.#cachedBody("formData");
498
- }
499
- /**
500
- * Adds validated data to the request.
501
- *
502
- * @param target - The target of the validation.
503
- * @param data - The validated data to add.
504
- */
505
- addValidatedData(target, data) {
506
- this.#validatedData[target] = data;
507
- }
508
- valid(target) {
509
- return this.#validatedData[target];
510
- }
511
- /**
512
- * `.url()` can get the request url strings.
513
- *
514
- * @see {@link https://hono.dev/docs/api/request#url}
515
- *
516
- * @example
517
- * ```ts
518
- * app.get('/about/me', (c) => {
519
- * const url = c.req.url // `http://localhost:8787/about/me`
520
- * ...
521
- * })
522
- * ```
523
- */
524
- get url() {
525
- return this.raw.url;
526
- }
527
- /**
528
- * `.method()` can get the method name of the request.
529
- *
530
- * @see {@link https://hono.dev/docs/api/request#method}
531
- *
532
- * @example
533
- * ```ts
534
- * app.get('/about/me', (c) => {
535
- * const method = c.req.method // `GET`
536
- * })
537
- * ```
538
- */
539
- get method() {
540
- return this.raw.method;
541
- }
542
- get [GET_MATCH_RESULT]() {
543
- return this.#matchResult;
544
- }
545
- /**
546
- * `.matchedRoutes()` can return a matched route in the handler
547
- *
548
- * @deprecated
549
- *
550
- * Use matchedRoutes helper defined in "hono/route" instead.
551
- *
552
- * @see {@link https://hono.dev/docs/api/request#matchedroutes}
553
- *
554
- * @example
555
- * ```ts
556
- * app.use('*', async function logger(c, next) {
557
- * await next()
558
- * c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
559
- * const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
560
- * console.log(
561
- * method,
562
- * ' ',
563
- * path,
564
- * ' '.repeat(Math.max(10 - path.length, 0)),
565
- * name,
566
- * i === c.req.routeIndex ? '<- respond from here' : ''
567
- * )
568
- * })
569
- * })
570
- * ```
571
- */
572
- get matchedRoutes() {
573
- return this.#matchResult[0].map(([[, route]]) => route);
574
- }
575
- /**
576
- * `routePath()` can retrieve the path registered within the handler
577
- *
578
- * @deprecated
579
- *
580
- * Use routePath helper defined in "hono/route" instead.
581
- *
582
- * @see {@link https://hono.dev/docs/api/request#routepath}
583
- *
584
- * @example
585
- * ```ts
586
- * app.get('/posts/:id', (c) => {
587
- * return c.json({ path: c.req.routePath })
588
- * })
589
- * ```
590
- */
591
- get routePath() {
592
- return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
593
- }
594
- };
595
-
596
- // node_modules/hono/dist/utils/html.js
597
- var HtmlEscapedCallbackPhase = {
598
- Stringify: 1,
599
- BeforeStream: 2,
600
- Stream: 3
601
- };
602
- var raw = (value, callbacks) => {
603
- const escapedString = new String(value);
604
- escapedString.isEscaped = true;
605
- escapedString.callbacks = callbacks;
606
- return escapedString;
607
- };
608
- var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
609
- if (typeof str === "object" && !(str instanceof String)) {
610
- if (!(str instanceof Promise)) {
611
- str = str.toString();
612
- }
613
- if (str instanceof Promise) {
614
- str = await str;
615
- }
616
- }
617
- const callbacks = str.callbacks;
618
- if (!callbacks?.length) {
619
- return Promise.resolve(str);
620
- }
621
- if (buffer) {
622
- buffer[0] += str;
623
- } else {
624
- buffer = [str];
625
- }
626
- const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then(
627
- (res) => Promise.all(
628
- res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))
629
- ).then(() => buffer[0])
630
- );
631
- if (preserveCallbacks) {
632
- return raw(await resStr, callbacks);
633
- } else {
634
- return resStr;
635
- }
636
- };
637
-
638
- // node_modules/hono/dist/context.js
639
- var TEXT_PLAIN = "text/plain; charset=UTF-8";
640
- var setDefaultContentType = (contentType, headers) => {
641
- return {
642
- "Content-Type": contentType,
643
- ...headers
644
- };
645
- };
646
- var createResponseInstance = (body, init) => new Response(body, init);
647
- var Context = class {
648
- #rawRequest;
649
- #req;
650
- /**
651
- * `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers.
652
- *
653
- * @see {@link https://hono.dev/docs/api/context#env}
654
- *
655
- * @example
656
- * ```ts
657
- * // Environment object for Cloudflare Workers
658
- * app.get('*', async c => {
659
- * const counter = c.env.COUNTER
660
- * })
661
- * ```
662
- */
663
- env = {};
664
- #var;
665
- finalized = false;
666
- /**
667
- * `.error` can get the error object from the middleware if the Handler throws an error.
668
- *
669
- * @see {@link https://hono.dev/docs/api/context#error}
670
- *
671
- * @example
672
- * ```ts
673
- * app.use('*', async (c, next) => {
674
- * await next()
675
- * if (c.error) {
676
- * // do something...
677
- * }
678
- * })
679
- * ```
680
- */
681
- error;
682
- #status;
683
- #executionCtx;
684
- #res;
685
- #layout;
686
- #renderer;
687
- #notFoundHandler;
688
- #preparedHeaders;
689
- #matchResult;
690
- #path;
691
- /**
692
- * Creates an instance of the Context class.
693
- *
694
- * @param req - The Request object.
695
- * @param options - Optional configuration options for the context.
696
- */
697
- constructor(req, options) {
698
- this.#rawRequest = req;
699
- if (options) {
700
- this.#executionCtx = options.executionCtx;
701
- this.env = options.env;
702
- this.#notFoundHandler = options.notFoundHandler;
703
- this.#path = options.path;
704
- this.#matchResult = options.matchResult;
705
- }
706
- }
707
- /**
708
- * `.req` is the instance of {@link HonoRequest}.
709
- */
710
- get req() {
711
- this.#req ??= new HonoRequest(this.#rawRequest, this.#path, this.#matchResult);
712
- return this.#req;
713
- }
714
- /**
715
- * @see {@link https://hono.dev/docs/api/context#event}
716
- * The FetchEvent associated with the current request.
717
- *
718
- * @throws Will throw an error if the context does not have a FetchEvent.
719
- */
720
- get event() {
721
- if (this.#executionCtx && "respondWith" in this.#executionCtx) {
722
- return this.#executionCtx;
723
- } else {
724
- throw Error("This context has no FetchEvent");
725
- }
726
- }
727
- /**
728
- * @see {@link https://hono.dev/docs/api/context#executionctx}
729
- * The ExecutionContext associated with the current request.
730
- *
731
- * @throws Will throw an error if the context does not have an ExecutionContext.
732
- */
733
- get executionCtx() {
734
- if (this.#executionCtx) {
735
- return this.#executionCtx;
736
- } else {
737
- throw Error("This context has no ExecutionContext");
738
- }
739
- }
740
- /**
741
- * @see {@link https://hono.dev/docs/api/context#res}
742
- * The Response object for the current request.
743
- */
744
- get res() {
745
- return this.#res ||= createResponseInstance(null, {
746
- headers: this.#preparedHeaders ??= new Headers()
747
- });
748
- }
749
- /**
750
- * Sets the Response object for the current request.
751
- *
752
- * @param _res - The Response object to set.
753
- */
754
- set res(_res) {
755
- if (this.#res && _res) {
756
- _res = createResponseInstance(_res.body, _res);
757
- for (const [k, v] of this.#res.headers.entries()) {
758
- if (k === "content-type") {
759
- continue;
760
- }
761
- if (k === "set-cookie") {
762
- const cookies = this.#res.headers.getSetCookie();
763
- _res.headers.delete("set-cookie");
764
- for (const cookie of cookies) {
765
- _res.headers.append("set-cookie", cookie);
766
- }
767
- } else {
768
- _res.headers.set(k, v);
769
- }
770
- }
771
- }
772
- this.#res = _res;
773
- this.finalized = true;
774
- }
775
- /**
776
- * `.render()` can create a response within a layout.
777
- *
778
- * @see {@link https://hono.dev/docs/api/context#render-setrenderer}
779
- *
780
- * @example
781
- * ```ts
782
- * app.get('/', (c) => {
783
- * return c.render('Hello!')
784
- * })
785
- * ```
786
- */
787
- render = (...args) => {
788
- this.#renderer ??= (content) => this.html(content);
789
- return this.#renderer(...args);
790
- };
791
- /**
792
- * Sets the layout for the response.
793
- *
794
- * @param layout - The layout to set.
795
- * @returns The layout function.
796
- */
797
- setLayout = (layout) => this.#layout = layout;
798
- /**
799
- * Gets the current layout for the response.
800
- *
801
- * @returns The current layout function.
802
- */
803
- getLayout = () => this.#layout;
804
- /**
805
- * `.setRenderer()` can set the layout in the custom middleware.
806
- *
807
- * @see {@link https://hono.dev/docs/api/context#render-setrenderer}
808
- *
809
- * @example
810
- * ```tsx
811
- * app.use('*', async (c, next) => {
812
- * c.setRenderer((content) => {
813
- * return c.html(
814
- * <html>
815
- * <body>
816
- * <p>{content}</p>
817
- * </body>
818
- * </html>
819
- * )
820
- * })
821
- * await next()
822
- * })
823
- * ```
824
- */
825
- setRenderer = (renderer) => {
826
- this.#renderer = renderer;
827
- };
828
- /**
829
- * `.header()` can set headers.
830
- *
831
- * @see {@link https://hono.dev/docs/api/context#header}
832
- *
833
- * @example
834
- * ```ts
835
- * app.get('/welcome', (c) => {
836
- * // Set headers
837
- * c.header('X-Message', 'Hello!')
838
- * c.header('Content-Type', 'text/plain')
839
- *
840
- * return c.body('Thank you for coming')
841
- * })
842
- * ```
843
- */
844
- header = (name, value, options) => {
845
- if (this.finalized) {
846
- this.#res = createResponseInstance(this.#res.body, this.#res);
847
- }
848
- const headers = this.#res ? this.#res.headers : this.#preparedHeaders ??= new Headers();
849
- if (value === void 0) {
850
- headers.delete(name);
851
- } else if (options?.append) {
852
- headers.append(name, value);
853
- } else {
854
- headers.set(name, value);
855
- }
856
- };
857
- status = (status) => {
858
- this.#status = status;
859
- };
860
- /**
861
- * `.set()` can set the value specified by the key.
862
- *
863
- * @see {@link https://hono.dev/docs/api/context#set-get}
864
- *
865
- * @example
866
- * ```ts
867
- * app.use('*', async (c, next) => {
868
- * c.set('message', 'Hono is hot!!')
869
- * await next()
870
- * })
871
- * ```
872
- */
873
- set = (key, value) => {
874
- this.#var ??= /* @__PURE__ */ new Map();
875
- this.#var.set(key, value);
876
- };
877
- /**
878
- * `.get()` can use the value specified by the key.
879
- *
880
- * @see {@link https://hono.dev/docs/api/context#set-get}
881
- *
882
- * @example
883
- * ```ts
884
- * app.get('/', (c) => {
885
- * const message = c.get('message')
886
- * return c.text(`The message is "${message}"`)
887
- * })
888
- * ```
889
- */
890
- get = (key) => {
891
- return this.#var ? this.#var.get(key) : void 0;
892
- };
893
- /**
894
- * `.var` can access the value of a variable.
895
- *
896
- * @see {@link https://hono.dev/docs/api/context#var}
897
- *
898
- * @example
899
- * ```ts
900
- * const result = c.var.client.oneMethod()
901
- * ```
902
- */
903
- // c.var.propName is a read-only
904
- get var() {
905
- if (!this.#var) {
906
- return {};
907
- }
908
- return Object.fromEntries(this.#var);
909
- }
910
- #newResponse(data, arg, headers) {
911
- const responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders ?? new Headers();
912
- if (typeof arg === "object" && "headers" in arg) {
913
- const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers);
914
- for (const [key, value] of argHeaders) {
915
- if (key.toLowerCase() === "set-cookie") {
916
- responseHeaders.append(key, value);
917
- } else {
918
- responseHeaders.set(key, value);
919
- }
920
- }
921
- }
922
- if (headers) {
923
- for (const [k, v] of Object.entries(headers)) {
924
- if (typeof v === "string") {
925
- responseHeaders.set(k, v);
926
- } else {
927
- responseHeaders.delete(k);
928
- for (const v2 of v) {
929
- responseHeaders.append(k, v2);
930
- }
931
- }
932
- }
933
- }
934
- const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
935
- return createResponseInstance(data, { status, headers: responseHeaders });
936
- }
937
- newResponse = (...args) => this.#newResponse(...args);
938
- /**
939
- * `.body()` can return the HTTP response.
940
- * You can set headers with `.header()` and set HTTP status code with `.status`.
941
- * This can also be set in `.text()`, `.json()` and so on.
942
- *
943
- * @see {@link https://hono.dev/docs/api/context#body}
944
- *
945
- * @example
946
- * ```ts
947
- * app.get('/welcome', (c) => {
948
- * // Set headers
949
- * c.header('X-Message', 'Hello!')
950
- * c.header('Content-Type', 'text/plain')
951
- * // Set HTTP status code
952
- * c.status(201)
953
- *
954
- * // Return the response body
955
- * return c.body('Thank you for coming')
956
- * })
957
- * ```
958
- */
959
- body = (data, arg, headers) => this.#newResponse(data, arg, headers);
960
- /**
961
- * `.text()` can render text as `Content-Type:text/plain`.
962
- *
963
- * @see {@link https://hono.dev/docs/api/context#text}
964
- *
965
- * @example
966
- * ```ts
967
- * app.get('/say', (c) => {
968
- * return c.text('Hello!')
969
- * })
970
- * ```
971
- */
972
- text = (text, arg, headers) => {
973
- return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(text) : this.#newResponse(
974
- text,
975
- arg,
976
- setDefaultContentType(TEXT_PLAIN, headers)
977
- );
978
- };
979
- /**
980
- * `.json()` can render JSON as `Content-Type:application/json`.
981
- *
982
- * @see {@link https://hono.dev/docs/api/context#json}
983
- *
984
- * @example
985
- * ```ts
986
- * app.get('/api', (c) => {
987
- * return c.json({ message: 'Hello!' })
988
- * })
989
- * ```
990
- */
991
- json = (object, arg, headers) => {
992
- return this.#newResponse(
993
- JSON.stringify(object),
994
- arg,
995
- setDefaultContentType("application/json", headers)
996
- );
997
- };
998
- html = (html, arg, headers) => {
999
- const res = (html2) => this.#newResponse(html2, arg, setDefaultContentType("text/html; charset=UTF-8", headers));
1000
- return typeof html === "object" ? resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res) : res(html);
1001
- };
1002
- /**
1003
- * `.redirect()` can Redirect, default status code is 302.
1004
- *
1005
- * @see {@link https://hono.dev/docs/api/context#redirect}
1006
- *
1007
- * @example
1008
- * ```ts
1009
- * app.get('/redirect', (c) => {
1010
- * return c.redirect('/')
1011
- * })
1012
- * app.get('/redirect-permanently', (c) => {
1013
- * return c.redirect('/', 301)
1014
- * })
1015
- * ```
1016
- */
1017
- redirect = (location, status) => {
1018
- const locationString = String(location);
1019
- this.header(
1020
- "Location",
1021
- // Multibyes should be encoded
1022
- // eslint-disable-next-line no-control-regex
1023
- !/[^\x00-\xFF]/.test(locationString) ? locationString : encodeURI(locationString)
1024
- );
1025
- return this.newResponse(null, status ?? 302);
1026
- };
1027
- /**
1028
- * `.notFound()` can return the Not Found Response.
1029
- *
1030
- * @see {@link https://hono.dev/docs/api/context#notfound}
1031
- *
1032
- * @example
1033
- * ```ts
1034
- * app.get('/notfound', (c) => {
1035
- * return c.notFound()
1036
- * })
1037
- * ```
1038
- */
1039
- notFound = () => {
1040
- this.#notFoundHandler ??= () => createResponseInstance();
1041
- return this.#notFoundHandler(this);
1042
- };
1043
- };
1044
-
1045
- // node_modules/hono/dist/router.js
1046
- var METHOD_NAME_ALL = "ALL";
1047
- var METHOD_NAME_ALL_LOWERCASE = "all";
1048
- var METHODS = ["get", "post", "put", "delete", "options", "patch"];
1049
- var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built.";
1050
- var UnsupportedPathError = class extends Error {
1051
- };
1052
-
1053
- // node_modules/hono/dist/utils/constants.js
1054
- var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
1055
-
1056
- // node_modules/hono/dist/hono-base.js
1057
- var notFoundHandler = (c) => {
1058
- return c.text("404 Not Found", 404);
1059
- };
1060
- var errorHandler = (err, c) => {
1061
- if ("getResponse" in err) {
1062
- const res = err.getResponse();
1063
- return c.newResponse(res.body, res);
1064
- }
1065
- console.error(err);
1066
- return c.text("Internal Server Error", 500);
1067
- };
1068
- var Hono = class _Hono {
1069
- get;
1070
- post;
1071
- put;
1072
- delete;
1073
- options;
1074
- patch;
1075
- all;
1076
- on;
1077
- use;
1078
- /*
1079
- This class is like an abstract class and does not have a router.
1080
- To use it, inherit the class and implement router in the constructor.
1081
- */
1082
- router;
1083
- getPath;
1084
- // Cannot use `#` because it requires visibility at JavaScript runtime.
1085
- _basePath = "/";
1086
- #path = "/";
1087
- routes = [];
1088
- constructor(options = {}) {
1089
- const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE];
1090
- allMethods.forEach((method) => {
1091
- this[method] = (args1, ...args) => {
1092
- if (typeof args1 === "string") {
1093
- this.#path = args1;
1094
- } else {
1095
- this.#addRoute(method, this.#path, args1);
1096
- }
1097
- args.forEach((handler) => {
1098
- this.#addRoute(method, this.#path, handler);
1099
- });
1100
- return this;
1101
- };
1102
- });
1103
- this.on = (method, path, ...handlers) => {
1104
- for (const p of [path].flat()) {
1105
- this.#path = p;
1106
- for (const m of [method].flat()) {
1107
- handlers.map((handler) => {
1108
- this.#addRoute(m.toUpperCase(), this.#path, handler);
1109
- });
1110
- }
1111
- }
1112
- return this;
1113
- };
1114
- this.use = (arg1, ...handlers) => {
1115
- if (typeof arg1 === "string") {
1116
- this.#path = arg1;
1117
- } else {
1118
- this.#path = "*";
1119
- handlers.unshift(arg1);
1120
- }
1121
- handlers.forEach((handler) => {
1122
- this.#addRoute(METHOD_NAME_ALL, this.#path, handler);
1123
- });
1124
- return this;
1125
- };
1126
- const { strict, ...optionsWithoutStrict } = options;
1127
- Object.assign(this, optionsWithoutStrict);
1128
- this.getPath = strict ?? true ? options.getPath ?? getPath : getPathNoStrict;
1129
- }
1130
- #clone() {
1131
- const clone = new _Hono({
1132
- router: this.router,
1133
- getPath: this.getPath
1134
- });
1135
- clone.errorHandler = this.errorHandler;
1136
- clone.#notFoundHandler = this.#notFoundHandler;
1137
- clone.routes = this.routes;
1138
- return clone;
1139
- }
1140
- #notFoundHandler = notFoundHandler;
1141
- // Cannot use `#` because it requires visibility at JavaScript runtime.
1142
- errorHandler = errorHandler;
1143
- /**
1144
- * `.route()` allows grouping other Hono instance in routes.
1145
- *
1146
- * @see {@link https://hono.dev/docs/api/routing#grouping}
1147
- *
1148
- * @param {string} path - base Path
1149
- * @param {Hono} app - other Hono instance
1150
- * @returns {Hono} routed Hono instance
1151
- *
1152
- * @example
1153
- * ```ts
1154
- * const app = new Hono()
1155
- * const app2 = new Hono()
1156
- *
1157
- * app2.get("/user", (c) => c.text("user"))
1158
- * app.route("/api", app2) // GET /api/user
1159
- * ```
1160
- */
1161
- route(path, app) {
1162
- const subApp = this.basePath(path);
1163
- app.routes.map((r) => {
1164
- let handler;
1165
- if (app.errorHandler === errorHandler) {
1166
- handler = r.handler;
1167
- } else {
1168
- handler = async (c, next) => (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res;
1169
- handler[COMPOSED_HANDLER] = r.handler;
1170
- }
1171
- subApp.#addRoute(r.method, r.path, handler);
1172
- });
1173
- return this;
1174
- }
1175
- /**
1176
- * `.basePath()` allows base paths to be specified.
1177
- *
1178
- * @see {@link https://hono.dev/docs/api/routing#base-path}
1179
- *
1180
- * @param {string} path - base Path
1181
- * @returns {Hono} changed Hono instance
1182
- *
1183
- * @example
1184
- * ```ts
1185
- * const api = new Hono().basePath('/api')
1186
- * ```
1187
- */
1188
- basePath(path) {
1189
- const subApp = this.#clone();
1190
- subApp._basePath = mergePath(this._basePath, path);
1191
- return subApp;
1192
- }
1193
- /**
1194
- * `.onError()` handles an error and returns a customized Response.
1195
- *
1196
- * @see {@link https://hono.dev/docs/api/hono#error-handling}
1197
- *
1198
- * @param {ErrorHandler} handler - request Handler for error
1199
- * @returns {Hono} changed Hono instance
1200
- *
1201
- * @example
1202
- * ```ts
1203
- * app.onError((err, c) => {
1204
- * console.error(`${err}`)
1205
- * return c.text('Custom Error Message', 500)
1206
- * })
1207
- * ```
1208
- */
1209
- onError = (handler) => {
1210
- this.errorHandler = handler;
1211
- return this;
1212
- };
1213
- /**
1214
- * `.notFound()` allows you to customize a Not Found Response.
1215
- *
1216
- * @see {@link https://hono.dev/docs/api/hono#not-found}
1217
- *
1218
- * @param {NotFoundHandler} handler - request handler for not-found
1219
- * @returns {Hono} changed Hono instance
1220
- *
1221
- * @example
1222
- * ```ts
1223
- * app.notFound((c) => {
1224
- * return c.text('Custom 404 Message', 404)
1225
- * })
1226
- * ```
1227
- */
1228
- notFound = (handler) => {
1229
- this.#notFoundHandler = handler;
1230
- return this;
1231
- };
1232
- /**
1233
- * `.mount()` allows you to mount applications built with other frameworks into your Hono application.
1234
- *
1235
- * @see {@link https://hono.dev/docs/api/hono#mount}
1236
- *
1237
- * @param {string} path - base Path
1238
- * @param {Function} applicationHandler - other Request Handler
1239
- * @param {MountOptions} [options] - options of `.mount()`
1240
- * @returns {Hono} mounted Hono instance
1241
- *
1242
- * @example
1243
- * ```ts
1244
- * import { Router as IttyRouter } from 'itty-router'
1245
- * import { Hono } from 'hono'
1246
- * // Create itty-router application
1247
- * const ittyRouter = IttyRouter()
1248
- * // GET /itty-router/hello
1249
- * ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
1250
- *
1251
- * const app = new Hono()
1252
- * app.mount('/itty-router', ittyRouter.handle)
1253
- * ```
1254
- *
1255
- * @example
1256
- * ```ts
1257
- * const app = new Hono()
1258
- * // Send the request to another application without modification.
1259
- * app.mount('/app', anotherApp, {
1260
- * replaceRequest: (req) => req,
1261
- * })
1262
- * ```
1263
- */
1264
- mount(path, applicationHandler, options) {
1265
- let replaceRequest;
1266
- let optionHandler;
1267
- if (options) {
1268
- if (typeof options === "function") {
1269
- optionHandler = options;
1270
- } else {
1271
- optionHandler = options.optionHandler;
1272
- if (options.replaceRequest === false) {
1273
- replaceRequest = (request) => request;
1274
- } else {
1275
- replaceRequest = options.replaceRequest;
1276
- }
1277
- }
1278
- }
1279
- const getOptions = optionHandler ? (c) => {
1280
- const options2 = optionHandler(c);
1281
- return Array.isArray(options2) ? options2 : [options2];
1282
- } : (c) => {
1283
- let executionContext = void 0;
1284
- try {
1285
- executionContext = c.executionCtx;
1286
- } catch {
1287
- }
1288
- return [c.env, executionContext];
1289
- };
1290
- replaceRequest ||= (() => {
1291
- const mergedPath = mergePath(this._basePath, path);
1292
- const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
1293
- return (request) => {
1294
- const url = new URL(request.url);
1295
- url.pathname = url.pathname.slice(pathPrefixLength) || "/";
1296
- return new Request(url, request);
1297
- };
1298
- })();
1299
- const handler = async (c, next) => {
1300
- const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c));
1301
- if (res) {
1302
- return res;
1303
- }
1304
- await next();
1305
- };
1306
- this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler);
1307
- return this;
1308
- }
1309
- #addRoute(method, path, handler) {
1310
- method = method.toUpperCase();
1311
- path = mergePath(this._basePath, path);
1312
- const r = { basePath: this._basePath, path, method, handler };
1313
- this.router.add(method, path, [handler, r]);
1314
- this.routes.push(r);
1315
- }
1316
- #handleError(err, c) {
1317
- if (err instanceof Error) {
1318
- return this.errorHandler(err, c);
1319
- }
1320
- throw err;
1321
- }
1322
- #dispatch(request, executionCtx, env, method) {
1323
- if (method === "HEAD") {
1324
- return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
1325
- }
1326
- const path = this.getPath(request, { env });
1327
- const matchResult = this.router.match(method, path);
1328
- const c = new Context(request, {
1329
- path,
1330
- matchResult,
1331
- env,
1332
- executionCtx,
1333
- notFoundHandler: this.#notFoundHandler
1334
- });
1335
- if (matchResult[0].length === 1) {
1336
- let res;
1337
- try {
1338
- res = matchResult[0][0][0][0](c, async () => {
1339
- c.res = await this.#notFoundHandler(c);
1340
- });
1341
- } catch (err) {
1342
- return this.#handleError(err, c);
1343
- }
1344
- return res instanceof Promise ? res.then(
1345
- (resolved) => resolved || (c.finalized ? c.res : this.#notFoundHandler(c))
1346
- ).catch((err) => this.#handleError(err, c)) : res ?? this.#notFoundHandler(c);
1347
- }
1348
- const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler);
1349
- return (async () => {
1350
- try {
1351
- const context = await composed(c);
1352
- if (!context.finalized) {
1353
- throw new Error(
1354
- "Context is not finalized. Did you forget to return a Response object or `await next()`?"
1355
- );
1356
- }
1357
- return context.res;
1358
- } catch (err) {
1359
- return this.#handleError(err, c);
1360
- }
1361
- })();
1362
- }
1363
- /**
1364
- * `.fetch()` will be entry point of your app.
1365
- *
1366
- * @see {@link https://hono.dev/docs/api/hono#fetch}
1367
- *
1368
- * @param {Request} request - request Object of request
1369
- * @param {Env} Env - env Object
1370
- * @param {ExecutionContext} - context of execution
1371
- * @returns {Response | Promise<Response>} response of request
1372
- *
1373
- */
1374
- fetch = (request, ...rest) => {
1375
- return this.#dispatch(request, rest[1], rest[0], request.method);
1376
- };
1377
- /**
1378
- * `.request()` is a useful method for testing.
1379
- * You can pass a URL or pathname to send a GET request.
1380
- * app will return a Response object.
1381
- * ```ts
1382
- * test('GET /hello is ok', async () => {
1383
- * const res = await app.request('/hello')
1384
- * expect(res.status).toBe(200)
1385
- * })
1386
- * ```
1387
- * @see https://hono.dev/docs/api/hono#request
1388
- */
1389
- request = (input, requestInit, Env, executionCtx) => {
1390
- if (input instanceof Request) {
1391
- return this.fetch(requestInit ? new Request(input, requestInit) : input, Env, executionCtx);
1392
- }
1393
- input = input.toString();
1394
- return this.fetch(
1395
- new Request(
1396
- /^https?:\/\//.test(input) ? input : `http://localhost${mergePath("/", input)}`,
1397
- requestInit
1398
- ),
1399
- Env,
1400
- executionCtx
1401
- );
1402
- };
1403
- /**
1404
- * `.fire()` automatically adds a global fetch event listener.
1405
- * This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.
1406
- * @deprecated
1407
- * Use `fire` from `hono/service-worker` instead.
1408
- * ```ts
1409
- * import { Hono } from 'hono'
1410
- * import { fire } from 'hono/service-worker'
1411
- *
1412
- * const app = new Hono()
1413
- * // ...
1414
- * fire(app)
1415
- * ```
1416
- * @see https://hono.dev/docs/api/hono#fire
1417
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
1418
- * @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/
1419
- */
1420
- fire = () => {
1421
- addEventListener("fetch", (event) => {
1422
- event.respondWith(this.#dispatch(event.request, event, void 0, event.request.method));
1423
- });
1424
- };
1425
- };
1426
-
1427
- // node_modules/hono/dist/router/reg-exp-router/matcher.js
1428
- var emptyParam = [];
1429
- function match(method, path) {
1430
- const matchers = this.buildAllMatchers();
1431
- const match2 = ((method2, path2) => {
1432
- const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
1433
- const staticMatch = matcher[2][path2];
1434
- if (staticMatch) {
1435
- return staticMatch;
1436
- }
1437
- const match3 = path2.match(matcher[0]);
1438
- if (!match3) {
1439
- return [[], emptyParam];
1440
- }
1441
- const index = match3.indexOf("", 1);
1442
- return [matcher[1][index], match3];
1443
- });
1444
- this.match = match2;
1445
- return match2(method, path);
1446
- }
1447
-
1448
- // node_modules/hono/dist/router/reg-exp-router/node.js
1449
- var LABEL_REG_EXP_STR = "[^/]+";
1450
- var ONLY_WILDCARD_REG_EXP_STR = ".*";
1451
- var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
1452
- var PATH_ERROR = /* @__PURE__ */ Symbol();
1453
- var regExpMetaChars = new Set(".\\+*[^]$()");
1454
- function compareKey(a, b) {
1455
- if (a.length === 1) {
1456
- return b.length === 1 ? a < b ? -1 : 1 : -1;
1457
- }
1458
- if (b.length === 1) {
1459
- return 1;
1460
- }
1461
- if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {
1462
- return 1;
1463
- } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) {
1464
- return -1;
1465
- }
1466
- if (a === LABEL_REG_EXP_STR) {
1467
- return 1;
1468
- } else if (b === LABEL_REG_EXP_STR) {
1469
- return -1;
1470
- }
1471
- return a.length === b.length ? a < b ? -1 : 1 : b.length - a.length;
1472
- }
1473
- var Node = class _Node {
1474
- #index;
1475
- #varIndex;
1476
- #children = /* @__PURE__ */ Object.create(null);
1477
- insert(tokens, index, paramMap, context, pathErrorCheckOnly) {
1478
- if (tokens.length === 0) {
1479
- if (this.#index !== void 0) {
1480
- throw PATH_ERROR;
1481
- }
1482
- if (pathErrorCheckOnly) {
1483
- return;
1484
- }
1485
- this.#index = index;
1486
- return;
1487
- }
1488
- const [token, ...restTokens] = tokens;
1489
- const pattern = token === "*" ? restTokens.length === 0 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
1490
- let node;
1491
- if (pattern) {
1492
- const name = pattern[1];
1493
- let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
1494
- if (name && pattern[2]) {
1495
- if (regexpStr === ".*") {
1496
- throw PATH_ERROR;
1497
- }
1498
- regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
1499
- if (/\((?!\?:)/.test(regexpStr)) {
1500
- throw PATH_ERROR;
1501
- }
1502
- }
1503
- node = this.#children[regexpStr];
1504
- if (!node) {
1505
- if (Object.keys(this.#children).some(
1506
- (k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR
1507
- )) {
1508
- throw PATH_ERROR;
1509
- }
1510
- if (pathErrorCheckOnly) {
1511
- return;
1512
- }
1513
- node = this.#children[regexpStr] = new _Node();
1514
- if (name !== "") {
1515
- node.#varIndex = context.varIndex++;
1516
- }
1517
- }
1518
- if (!pathErrorCheckOnly && name !== "") {
1519
- paramMap.push([name, node.#varIndex]);
1520
- }
1521
- } else {
1522
- node = this.#children[token];
1523
- if (!node) {
1524
- if (Object.keys(this.#children).some(
1525
- (k) => k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR
1526
- )) {
1527
- throw PATH_ERROR;
1528
- }
1529
- if (pathErrorCheckOnly) {
1530
- return;
1531
- }
1532
- node = this.#children[token] = new _Node();
1533
- }
1534
- }
1535
- node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly);
1536
- }
1537
- buildRegExpStr() {
1538
- const childKeys = Object.keys(this.#children).sort(compareKey);
1539
- const strList = childKeys.map((k) => {
1540
- const c = this.#children[k];
1541
- return (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + c.buildRegExpStr();
1542
- });
1543
- if (typeof this.#index === "number") {
1544
- strList.unshift(`#${this.#index}`);
1545
- }
1546
- if (strList.length === 0) {
1547
- return "";
1548
- }
1549
- if (strList.length === 1) {
1550
- return strList[0];
1551
- }
1552
- return "(?:" + strList.join("|") + ")";
1553
- }
1554
- };
1555
-
1556
- // node_modules/hono/dist/router/reg-exp-router/trie.js
1557
- var Trie = class {
1558
- #context = { varIndex: 0 };
1559
- #root = new Node();
1560
- insert(path, index, pathErrorCheckOnly) {
1561
- const paramAssoc = [];
1562
- const groups = [];
1563
- for (let i = 0; ; ) {
1564
- let replaced = false;
1565
- path = path.replace(/\{[^}]+\}/g, (m) => {
1566
- const mark = `@\\${i}`;
1567
- groups[i] = [mark, m];
1568
- i++;
1569
- replaced = true;
1570
- return mark;
1571
- });
1572
- if (!replaced) {
1573
- break;
1574
- }
1575
- }
1576
- const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
1577
- for (let i = groups.length - 1; i >= 0; i--) {
1578
- const [mark] = groups[i];
1579
- for (let j = tokens.length - 1; j >= 0; j--) {
1580
- if (tokens[j].indexOf(mark) !== -1) {
1581
- tokens[j] = tokens[j].replace(mark, groups[i][1]);
1582
- break;
1583
- }
1584
- }
1585
- }
1586
- this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly);
1587
- return paramAssoc;
1588
- }
1589
- buildRegExp() {
1590
- let regexp = this.#root.buildRegExpStr();
1591
- if (regexp === "") {
1592
- return [/^$/, [], []];
1593
- }
1594
- let captureIndex = 0;
1595
- const indexReplacementMap = [];
1596
- const paramReplacementMap = [];
1597
- regexp = regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handlerIndex, paramIndex) => {
1598
- if (handlerIndex !== void 0) {
1599
- indexReplacementMap[++captureIndex] = Number(handlerIndex);
1600
- return "$()";
1601
- }
1602
- if (paramIndex !== void 0) {
1603
- paramReplacementMap[Number(paramIndex)] = ++captureIndex;
1604
- return "";
1605
- }
1606
- return "";
1607
- });
1608
- return [new RegExp(`^${regexp}`), indexReplacementMap, paramReplacementMap];
1609
- }
1610
- };
1611
-
1612
- // node_modules/hono/dist/router/reg-exp-router/router.js
1613
- var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
1614
- var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
1615
- function buildWildcardRegExp(path) {
1616
- return wildcardRegExpCache[path] ??= new RegExp(
1617
- path === "*" ? "" : `^${path.replace(
1618
- /\/\*$|([.\\+*[^\]$()])/g,
1619
- (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)"
1620
- )}$`
1621
- );
1622
- }
1623
- function clearWildcardRegExpCache() {
1624
- wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
1625
- }
1626
- function buildMatcherFromPreprocessedRoutes(routes) {
1627
- const trie = new Trie();
1628
- const handlerData = [];
1629
- if (routes.length === 0) {
1630
- return nullMatcher;
1631
- }
1632
- const routesWithStaticPathFlag = routes.map(
1633
- (route) => [!/\*|\/:/.test(route[0]), ...route]
1634
- ).sort(
1635
- ([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length
1636
- );
1637
- const staticMap = /* @__PURE__ */ Object.create(null);
1638
- for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) {
1639
- const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i];
1640
- if (pathErrorCheckOnly) {
1641
- staticMap[path] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
1642
- } else {
1643
- j++;
1644
- }
1645
- let paramAssoc;
1646
- try {
1647
- paramAssoc = trie.insert(path, j, pathErrorCheckOnly);
1648
- } catch (e) {
1649
- throw e === PATH_ERROR ? new UnsupportedPathError(path) : e;
1650
- }
1651
- if (pathErrorCheckOnly) {
1652
- continue;
1653
- }
1654
- handlerData[j] = handlers.map(([h, paramCount]) => {
1655
- const paramIndexMap = /* @__PURE__ */ Object.create(null);
1656
- paramCount -= 1;
1657
- for (; paramCount >= 0; paramCount--) {
1658
- const [key, value] = paramAssoc[paramCount];
1659
- paramIndexMap[key] = value;
1660
- }
1661
- return [h, paramIndexMap];
1662
- });
1663
- }
1664
- const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
1665
- for (let i = 0, len = handlerData.length; i < len; i++) {
1666
- for (let j = 0, len2 = handlerData[i].length; j < len2; j++) {
1667
- const map = handlerData[i][j]?.[1];
1668
- if (!map) {
1669
- continue;
1670
- }
1671
- const keys = Object.keys(map);
1672
- for (let k = 0, len3 = keys.length; k < len3; k++) {
1673
- map[keys[k]] = paramReplacementMap[map[keys[k]]];
1674
- }
1675
- }
1676
- }
1677
- const handlerMap = [];
1678
- for (const i in indexReplacementMap) {
1679
- handlerMap[i] = handlerData[indexReplacementMap[i]];
1680
- }
1681
- return [regexp, handlerMap, staticMap];
1682
- }
1683
- function findMiddleware(middleware, path) {
1684
- if (!middleware) {
1685
- return void 0;
1686
- }
1687
- for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
1688
- if (buildWildcardRegExp(k).test(path)) {
1689
- return [...middleware[k]];
1690
- }
1691
- }
1692
- return void 0;
1693
- }
1694
- var RegExpRouter = class {
1695
- name = "RegExpRouter";
1696
- #middleware;
1697
- #routes;
1698
- constructor() {
1699
- this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
1700
- this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
1701
- }
1702
- add(method, path, handler) {
1703
- const middleware = this.#middleware;
1704
- const routes = this.#routes;
1705
- if (!middleware || !routes) {
1706
- throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
1707
- }
1708
- if (!middleware[method]) {
1709
- ;
1710
- [middleware, routes].forEach((handlerMap) => {
1711
- handlerMap[method] = /* @__PURE__ */ Object.create(null);
1712
- Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => {
1713
- handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]];
1714
- });
1715
- });
1716
- }
1717
- if (path === "/*") {
1718
- path = "*";
1719
- }
1720
- const paramCount = (path.match(/\/:/g) || []).length;
1721
- if (/\*$/.test(path)) {
1722
- const re = buildWildcardRegExp(path);
1723
- if (method === METHOD_NAME_ALL) {
1724
- Object.keys(middleware).forEach((m) => {
1725
- middleware[m][path] ||= findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
1726
- });
1727
- } else {
1728
- middleware[method][path] ||= findMiddleware(middleware[method], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
1729
- }
1730
- Object.keys(middleware).forEach((m) => {
1731
- if (method === METHOD_NAME_ALL || method === m) {
1732
- Object.keys(middleware[m]).forEach((p) => {
1733
- re.test(p) && middleware[m][p].push([handler, paramCount]);
1734
- });
1735
- }
1736
- });
1737
- Object.keys(routes).forEach((m) => {
1738
- if (method === METHOD_NAME_ALL || method === m) {
1739
- Object.keys(routes[m]).forEach(
1740
- (p) => re.test(p) && routes[m][p].push([handler, paramCount])
1741
- );
1742
- }
1743
- });
1744
- return;
1745
- }
1746
- const paths = checkOptionalParameter(path) || [path];
1747
- for (let i = 0, len = paths.length; i < len; i++) {
1748
- const path2 = paths[i];
1749
- Object.keys(routes).forEach((m) => {
1750
- if (method === METHOD_NAME_ALL || method === m) {
1751
- routes[m][path2] ||= [
1752
- ...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
1753
- ];
1754
- routes[m][path2].push([handler, paramCount - len + i + 1]);
1755
- }
1756
- });
1757
- }
1758
- }
1759
- match = match;
1760
- buildAllMatchers() {
1761
- const matchers = /* @__PURE__ */ Object.create(null);
1762
- Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => {
1763
- matchers[method] ||= this.#buildMatcher(method);
1764
- });
1765
- this.#middleware = this.#routes = void 0;
1766
- clearWildcardRegExpCache();
1767
- return matchers;
1768
- }
1769
- #buildMatcher(method) {
1770
- const routes = [];
1771
- let hasOwnRoute = method === METHOD_NAME_ALL;
1772
- [this.#middleware, this.#routes].forEach((r) => {
1773
- const ownRoute = r[method] ? Object.keys(r[method]).map((path) => [path, r[method][path]]) : [];
1774
- if (ownRoute.length !== 0) {
1775
- hasOwnRoute ||= true;
1776
- routes.push(...ownRoute);
1777
- } else if (method !== METHOD_NAME_ALL) {
1778
- routes.push(
1779
- ...Object.keys(r[METHOD_NAME_ALL]).map((path) => [path, r[METHOD_NAME_ALL][path]])
1780
- );
1781
- }
1782
- });
1783
- if (!hasOwnRoute) {
1784
- return null;
1785
- } else {
1786
- return buildMatcherFromPreprocessedRoutes(routes);
1787
- }
1788
- }
1789
- };
1790
-
1791
- // node_modules/hono/dist/router/smart-router/router.js
1792
- var SmartRouter = class {
1793
- name = "SmartRouter";
1794
- #routers = [];
1795
- #routes = [];
1796
- constructor(init) {
1797
- this.#routers = init.routers;
1798
- }
1799
- add(method, path, handler) {
1800
- if (!this.#routes) {
1801
- throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
1802
- }
1803
- this.#routes.push([method, path, handler]);
1804
- }
1805
- match(method, path) {
1806
- if (!this.#routes) {
1807
- throw new Error("Fatal error");
1808
- }
1809
- const routers = this.#routers;
1810
- const routes = this.#routes;
1811
- const len = routers.length;
1812
- let i = 0;
1813
- let res;
1814
- for (; i < len; i++) {
1815
- const router = routers[i];
1816
- try {
1817
- for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) {
1818
- router.add(...routes[i2]);
1819
- }
1820
- res = router.match(method, path);
1821
- } catch (e) {
1822
- if (e instanceof UnsupportedPathError) {
1823
- continue;
1824
- }
1825
- throw e;
1826
- }
1827
- this.match = router.match.bind(router);
1828
- this.#routers = [router];
1829
- this.#routes = void 0;
1830
- break;
1831
- }
1832
- if (i === len) {
1833
- throw new Error("Fatal error");
1834
- }
1835
- this.name = `SmartRouter + ${this.activeRouter.name}`;
1836
- return res;
1837
- }
1838
- get activeRouter() {
1839
- if (this.#routes || this.#routers.length !== 1) {
1840
- throw new Error("No active router has been determined yet.");
1841
- }
1842
- return this.#routers[0];
1843
- }
1844
- };
1845
-
1846
- // node_modules/hono/dist/router/trie-router/node.js
1847
- var emptyParams = /* @__PURE__ */ Object.create(null);
1848
- var hasChildren = (children) => {
1849
- for (const _ in children) {
1850
- return true;
1851
- }
1852
- return false;
1853
- };
1854
- var Node2 = class _Node2 {
1855
- #methods;
1856
- #children;
1857
- #patterns;
1858
- #order = 0;
1859
- #params = emptyParams;
1860
- constructor(method, handler, children) {
1861
- this.#children = children || /* @__PURE__ */ Object.create(null);
1862
- this.#methods = [];
1863
- if (method && handler) {
1864
- const m = /* @__PURE__ */ Object.create(null);
1865
- m[method] = { handler, possibleKeys: [], score: 0 };
1866
- this.#methods = [m];
1867
- }
1868
- this.#patterns = [];
1869
- }
1870
- insert(method, path, handler) {
1871
- this.#order = ++this.#order;
1872
- let curNode = this;
1873
- const parts = splitRoutingPath(path);
1874
- const possibleKeys = [];
1875
- for (let i = 0, len = parts.length; i < len; i++) {
1876
- const p = parts[i];
1877
- const nextP = parts[i + 1];
1878
- const pattern = getPattern(p, nextP);
1879
- const key = Array.isArray(pattern) ? pattern[0] : p;
1880
- if (key in curNode.#children) {
1881
- curNode = curNode.#children[key];
1882
- if (pattern) {
1883
- possibleKeys.push(pattern[1]);
1884
- }
1885
- continue;
1886
- }
1887
- curNode.#children[key] = new _Node2();
1888
- if (pattern) {
1889
- curNode.#patterns.push(pattern);
1890
- possibleKeys.push(pattern[1]);
1891
- }
1892
- curNode = curNode.#children[key];
1893
- }
1894
- curNode.#methods.push({
1895
- [method]: {
1896
- handler,
1897
- possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),
1898
- score: this.#order
1899
- }
1900
- });
1901
- return curNode;
1902
- }
1903
- #pushHandlerSets(handlerSets, node, method, nodeParams, params) {
1904
- for (let i = 0, len = node.#methods.length; i < len; i++) {
1905
- const m = node.#methods[i];
1906
- const handlerSet = m[method] || m[METHOD_NAME_ALL];
1907
- const processedSet = {};
1908
- if (handlerSet !== void 0) {
1909
- handlerSet.params = /* @__PURE__ */ Object.create(null);
1910
- handlerSets.push(handlerSet);
1911
- if (nodeParams !== emptyParams || params && params !== emptyParams) {
1912
- for (let i2 = 0, len2 = handlerSet.possibleKeys.length; i2 < len2; i2++) {
1913
- const key = handlerSet.possibleKeys[i2];
1914
- const processed = processedSet[handlerSet.score];
1915
- handlerSet.params[key] = params?.[key] && !processed ? params[key] : nodeParams[key] ?? params?.[key];
1916
- processedSet[handlerSet.score] = true;
1917
- }
1918
- }
1919
- }
1920
- }
1921
- }
1922
- search(method, path) {
1923
- const handlerSets = [];
1924
- this.#params = emptyParams;
1925
- const curNode = this;
1926
- let curNodes = [curNode];
1927
- const parts = splitPath(path);
1928
- const curNodesQueue = [];
1929
- const len = parts.length;
1930
- let partOffsets = null;
1931
- for (let i = 0; i < len; i++) {
1932
- const part = parts[i];
1933
- const isLast = i === len - 1;
1934
- const tempNodes = [];
1935
- for (let j = 0, len2 = curNodes.length; j < len2; j++) {
1936
- const node = curNodes[j];
1937
- const nextNode = node.#children[part];
1938
- if (nextNode) {
1939
- nextNode.#params = node.#params;
1940
- if (isLast) {
1941
- if (nextNode.#children["*"]) {
1942
- this.#pushHandlerSets(handlerSets, nextNode.#children["*"], method, node.#params);
1943
- }
1944
- this.#pushHandlerSets(handlerSets, nextNode, method, node.#params);
1945
- } else {
1946
- tempNodes.push(nextNode);
1947
- }
1948
- }
1949
- for (let k = 0, len3 = node.#patterns.length; k < len3; k++) {
1950
- const pattern = node.#patterns[k];
1951
- const params = node.#params === emptyParams ? {} : { ...node.#params };
1952
- if (pattern === "*") {
1953
- const astNode = node.#children["*"];
1954
- if (astNode) {
1955
- this.#pushHandlerSets(handlerSets, astNode, method, node.#params);
1956
- astNode.#params = params;
1957
- tempNodes.push(astNode);
1958
- }
1959
- continue;
1960
- }
1961
- const [key, name, matcher] = pattern;
1962
- if (!part && !(matcher instanceof RegExp)) {
1963
- continue;
1964
- }
1965
- const child = node.#children[key];
1966
- if (matcher instanceof RegExp) {
1967
- if (partOffsets === null) {
1968
- partOffsets = new Array(len);
1969
- let offset = path[0] === "/" ? 1 : 0;
1970
- for (let p = 0; p < len; p++) {
1971
- partOffsets[p] = offset;
1972
- offset += parts[p].length + 1;
1973
- }
1974
- }
1975
- const restPathString = path.substring(partOffsets[i]);
1976
- const m = matcher.exec(restPathString);
1977
- if (m) {
1978
- params[name] = m[0];
1979
- this.#pushHandlerSets(handlerSets, child, method, node.#params, params);
1980
- if (hasChildren(child.#children)) {
1981
- child.#params = params;
1982
- const componentCount = m[0].match(/\//)?.length ?? 0;
1983
- const targetCurNodes = curNodesQueue[componentCount] ||= [];
1984
- targetCurNodes.push(child);
1985
- }
1986
- continue;
1987
- }
1988
- }
1989
- if (matcher === true || matcher.test(part)) {
1990
- params[name] = part;
1991
- if (isLast) {
1992
- this.#pushHandlerSets(handlerSets, child, method, params, node.#params);
1993
- if (child.#children["*"]) {
1994
- this.#pushHandlerSets(
1995
- handlerSets,
1996
- child.#children["*"],
1997
- method,
1998
- params,
1999
- node.#params
2000
- );
2001
- }
2002
- } else {
2003
- child.#params = params;
2004
- tempNodes.push(child);
2005
- }
2006
- }
2007
- }
2008
- }
2009
- const shifted = curNodesQueue.shift();
2010
- curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
2011
- }
2012
- if (handlerSets.length > 1) {
2013
- handlerSets.sort((a, b) => {
2014
- return a.score - b.score;
2015
- });
2016
- }
2017
- return [handlerSets.map(({ handler, params }) => [handler, params])];
2018
- }
2019
- };
2020
-
2021
- // node_modules/hono/dist/router/trie-router/router.js
2022
- var TrieRouter = class {
2023
- name = "TrieRouter";
2024
- #node;
2025
- constructor() {
2026
- this.#node = new Node2();
2027
- }
2028
- add(method, path, handler) {
2029
- const results = checkOptionalParameter(path);
2030
- if (results) {
2031
- for (let i = 0, len = results.length; i < len; i++) {
2032
- this.#node.insert(method, results[i], handler);
2033
- }
2034
- return;
2035
- }
2036
- this.#node.insert(method, path, handler);
2037
- }
2038
- match(method, path) {
2039
- return this.#node.search(method, path);
2040
- }
2041
- };
2042
-
2043
- // node_modules/hono/dist/hono.js
2044
- var Hono2 = class extends Hono {
2045
- /**
2046
- * Creates an instance of the Hono class.
2047
- *
2048
- * @param options - Optional configuration options for the Hono instance.
2049
- */
2050
- constructor(options = {}) {
2051
- super(options);
2052
- this.router = options.router ?? new SmartRouter({
2053
- routers: [new RegExpRouter(), new TrieRouter()]
2054
- });
2055
- }
2056
- };
2057
-
2058
- // node_modules/@hono/node-server/dist/index.mjs
2059
- import { createServer as createServerHTTP } from "http";
2060
- import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
2061
- import { Http2ServerRequest } from "http2";
2062
- import { Readable } from "stream";
2063
- import crypto from "crypto";
2064
- var RequestError = class extends Error {
2065
- constructor(message, options) {
2066
- super(message, options);
2067
- this.name = "RequestError";
2068
- }
2069
- };
2070
- var toRequestError = (e) => {
2071
- if (e instanceof RequestError) {
2072
- return e;
2073
- }
2074
- return new RequestError(e.message, { cause: e });
2075
- };
2076
- var GlobalRequest = global.Request;
2077
- var Request2 = class extends GlobalRequest {
2078
- constructor(input, options) {
2079
- if (typeof input === "object" && getRequestCache in input) {
2080
- input = input[getRequestCache]();
2081
- }
2082
- if (typeof options?.body?.getReader !== "undefined") {
2083
- ;
2084
- options.duplex ??= "half";
2085
- }
2086
- super(input, options);
2087
- }
2088
- };
2089
- var newHeadersFromIncoming = (incoming) => {
2090
- const headerRecord = [];
2091
- const rawHeaders = incoming.rawHeaders;
2092
- for (let i = 0; i < rawHeaders.length; i += 2) {
2093
- const { [i]: key, [i + 1]: value } = rawHeaders;
2094
- if (key.charCodeAt(0) !== /*:*/
2095
- 58) {
2096
- headerRecord.push([key, value]);
2097
- }
2098
- }
2099
- return new Headers(headerRecord);
2100
- };
2101
- var wrapBodyStream = /* @__PURE__ */ Symbol("wrapBodyStream");
2102
- var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
2103
- const init = {
2104
- method,
2105
- headers,
2106
- signal: abortController.signal
2107
- };
2108
- if (method === "TRACE") {
2109
- init.method = "GET";
2110
- const req = new Request2(url, init);
2111
- Object.defineProperty(req, "method", {
2112
- get() {
2113
- return "TRACE";
2114
- }
2115
- });
2116
- return req;
2117
- }
2118
- if (!(method === "GET" || method === "HEAD")) {
2119
- if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
2120
- init.body = new ReadableStream({
2121
- start(controller) {
2122
- controller.enqueue(incoming.rawBody);
2123
- controller.close();
2124
- }
2125
- });
2126
- } else if (incoming[wrapBodyStream]) {
2127
- let reader;
2128
- init.body = new ReadableStream({
2129
- async pull(controller) {
2130
- try {
2131
- reader ||= Readable.toWeb(incoming).getReader();
2132
- const { done, value } = await reader.read();
2133
- if (done) {
2134
- controller.close();
2135
- } else {
2136
- controller.enqueue(value);
2137
- }
2138
- } catch (error) {
2139
- controller.error(error);
2140
- }
2141
- }
2142
- });
2143
- } else {
2144
- init.body = Readable.toWeb(incoming);
2145
- }
2146
- }
2147
- return new Request2(url, init);
2148
- };
2149
- var getRequestCache = /* @__PURE__ */ Symbol("getRequestCache");
2150
- var requestCache = /* @__PURE__ */ Symbol("requestCache");
2151
- var incomingKey = /* @__PURE__ */ Symbol("incomingKey");
2152
- var urlKey = /* @__PURE__ */ Symbol("urlKey");
2153
- var headersKey = /* @__PURE__ */ Symbol("headersKey");
2154
- var abortControllerKey = /* @__PURE__ */ Symbol("abortControllerKey");
2155
- var getAbortController = /* @__PURE__ */ Symbol("getAbortController");
2156
- var requestPrototype = {
2157
- get method() {
2158
- return this[incomingKey].method || "GET";
2159
- },
2160
- get url() {
2161
- return this[urlKey];
2162
- },
2163
- get headers() {
2164
- return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
2165
- },
2166
- [getAbortController]() {
2167
- this[getRequestCache]();
2168
- return this[abortControllerKey];
2169
- },
2170
- [getRequestCache]() {
2171
- this[abortControllerKey] ||= new AbortController();
2172
- return this[requestCache] ||= newRequestFromIncoming(
2173
- this.method,
2174
- this[urlKey],
2175
- this.headers,
2176
- this[incomingKey],
2177
- this[abortControllerKey]
2178
- );
2179
- }
2180
- };
2181
- [
2182
- "body",
2183
- "bodyUsed",
2184
- "cache",
2185
- "credentials",
2186
- "destination",
2187
- "integrity",
2188
- "mode",
2189
- "redirect",
2190
- "referrer",
2191
- "referrerPolicy",
2192
- "signal",
2193
- "keepalive"
2194
- ].forEach((k) => {
2195
- Object.defineProperty(requestPrototype, k, {
2196
- get() {
2197
- return this[getRequestCache]()[k];
2198
- }
2199
- });
2200
- });
2201
- ["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
2202
- Object.defineProperty(requestPrototype, k, {
2203
- value: function() {
2204
- return this[getRequestCache]()[k]();
2205
- }
2206
- });
2207
- });
2208
- Object.setPrototypeOf(requestPrototype, Request2.prototype);
2209
- var newRequest = (incoming, defaultHostname) => {
2210
- const req = Object.create(requestPrototype);
2211
- req[incomingKey] = incoming;
2212
- const incomingUrl = incoming.url || "";
2213
- if (incomingUrl[0] !== "/" && // short-circuit for performance. most requests are relative URL.
2214
- (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
2215
- if (incoming instanceof Http2ServerRequest) {
2216
- throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
2217
- }
2218
- try {
2219
- const url2 = new URL(incomingUrl);
2220
- req[urlKey] = url2.href;
2221
- } catch (e) {
2222
- throw new RequestError("Invalid absolute URL", { cause: e });
2223
- }
2224
- return req;
2225
- }
2226
- const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
2227
- if (!host) {
2228
- throw new RequestError("Missing host header");
2229
- }
2230
- let scheme;
2231
- if (incoming instanceof Http2ServerRequest) {
2232
- scheme = incoming.scheme;
2233
- if (!(scheme === "http" || scheme === "https")) {
2234
- throw new RequestError("Unsupported scheme");
2235
- }
2236
- } else {
2237
- scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
2238
- }
2239
- const url = new URL(`${scheme}://${host}${incomingUrl}`);
2240
- if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
2241
- throw new RequestError("Invalid host header");
2242
- }
2243
- req[urlKey] = url.href;
2244
- return req;
2245
- };
2246
- var responseCache = /* @__PURE__ */ Symbol("responseCache");
2247
- var getResponseCache = /* @__PURE__ */ Symbol("getResponseCache");
2248
- var cacheKey = /* @__PURE__ */ Symbol("cache");
2249
- var GlobalResponse = global.Response;
2250
- var Response2 = class _Response {
2251
- #body;
2252
- #init;
2253
- [getResponseCache]() {
2254
- delete this[cacheKey];
2255
- return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
2256
- }
2257
- constructor(body, init) {
2258
- let headers;
2259
- this.#body = body;
2260
- if (init instanceof _Response) {
2261
- const cachedGlobalResponse = init[responseCache];
2262
- if (cachedGlobalResponse) {
2263
- this.#init = cachedGlobalResponse;
2264
- this[getResponseCache]();
2265
- return;
2266
- } else {
2267
- this.#init = init.#init;
2268
- headers = new Headers(init.#init.headers);
2269
- }
2270
- } else {
2271
- this.#init = init;
2272
- }
2273
- if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
2274
- ;
2275
- this[cacheKey] = [init?.status || 200, body, headers || init?.headers];
2276
- }
2277
- }
2278
- get headers() {
2279
- const cache = this[cacheKey];
2280
- if (cache) {
2281
- if (!(cache[2] instanceof Headers)) {
2282
- cache[2] = new Headers(
2283
- cache[2] || { "content-type": "text/plain; charset=UTF-8" }
2284
- );
2285
- }
2286
- return cache[2];
2287
- }
2288
- return this[getResponseCache]().headers;
2289
- }
2290
- get status() {
2291
- return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
2292
- }
2293
- get ok() {
2294
- const status = this.status;
2295
- return status >= 200 && status < 300;
2296
- }
2297
- };
2298
- ["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k) => {
2299
- Object.defineProperty(Response2.prototype, k, {
2300
- get() {
2301
- return this[getResponseCache]()[k];
2302
- }
2303
- });
2304
- });
2305
- ["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
2306
- Object.defineProperty(Response2.prototype, k, {
2307
- value: function() {
2308
- return this[getResponseCache]()[k]();
2309
- }
2310
- });
2311
- });
2312
- Object.setPrototypeOf(Response2, GlobalResponse);
2313
- Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
2314
- async function readWithoutBlocking(readPromise) {
2315
- return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
2316
- }
2317
- function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
2318
- const cancel = (error) => {
2319
- reader.cancel(error).catch(() => {
2320
- });
2321
- };
2322
- writable.on("close", cancel);
2323
- writable.on("error", cancel);
2324
- (currentReadPromise ?? reader.read()).then(flow, handleStreamError);
2325
- return reader.closed.finally(() => {
2326
- writable.off("close", cancel);
2327
- writable.off("error", cancel);
2328
- });
2329
- function handleStreamError(error) {
2330
- if (error) {
2331
- writable.destroy(error);
2332
- }
2333
- }
2334
- function onDrain() {
2335
- reader.read().then(flow, handleStreamError);
2336
- }
2337
- function flow({ done, value }) {
2338
- try {
2339
- if (done) {
2340
- writable.end();
2341
- } else if (!writable.write(value)) {
2342
- writable.once("drain", onDrain);
2343
- } else {
2344
- return reader.read().then(flow, handleStreamError);
2345
- }
2346
- } catch (e) {
2347
- handleStreamError(e);
2348
- }
2349
- }
2350
- }
2351
- function writeFromReadableStream(stream, writable) {
2352
- if (stream.locked) {
2353
- throw new TypeError("ReadableStream is locked.");
2354
- } else if (writable.destroyed) {
2355
- return;
2356
- }
2357
- return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
2358
- }
2359
- var buildOutgoingHttpHeaders = (headers) => {
2360
- const res = {};
2361
- if (!(headers instanceof Headers)) {
2362
- headers = new Headers(headers ?? void 0);
2363
- }
2364
- const cookies = [];
2365
- for (const [k, v] of headers) {
2366
- if (k === "set-cookie") {
2367
- cookies.push(v);
2368
- } else {
2369
- res[k] = v;
2370
- }
2371
- }
2372
- if (cookies.length > 0) {
2373
- res["set-cookie"] = cookies;
2374
- }
2375
- res["content-type"] ??= "text/plain; charset=UTF-8";
2376
- return res;
2377
- };
2378
- var X_ALREADY_SENT = "x-hono-already-sent";
2379
- if (typeof global.crypto === "undefined") {
2380
- global.crypto = crypto;
2381
- }
2382
- var outgoingEnded = /* @__PURE__ */ Symbol("outgoingEnded");
2383
- var handleRequestError = () => new Response(null, {
2384
- status: 400
2385
- });
2386
- var handleFetchError = (e) => new Response(null, {
2387
- status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
2388
- });
2389
- var handleResponseError = (e, outgoing) => {
2390
- const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
2391
- if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
2392
- console.info("The user aborted a request.");
2393
- } else {
2394
- console.error(e);
2395
- if (!outgoing.headersSent) {
2396
- outgoing.writeHead(500, { "Content-Type": "text/plain" });
2397
- }
2398
- outgoing.end(`Error: ${err.message}`);
2399
- outgoing.destroy(err);
2400
- }
2401
- };
2402
- var flushHeaders = (outgoing) => {
2403
- if ("flushHeaders" in outgoing && outgoing.writable) {
2404
- outgoing.flushHeaders();
2405
- }
2406
- };
2407
- var responseViaCache = async (res, outgoing) => {
2408
- let [status, body, header] = res[cacheKey];
2409
- let hasContentLength = false;
2410
- if (!header) {
2411
- header = { "content-type": "text/plain; charset=UTF-8" };
2412
- } else if (header instanceof Headers) {
2413
- hasContentLength = header.has("content-length");
2414
- header = buildOutgoingHttpHeaders(header);
2415
- } else if (Array.isArray(header)) {
2416
- const headerObj = new Headers(header);
2417
- hasContentLength = headerObj.has("content-length");
2418
- header = buildOutgoingHttpHeaders(headerObj);
2419
- } else {
2420
- for (const key in header) {
2421
- if (key.length === 14 && key.toLowerCase() === "content-length") {
2422
- hasContentLength = true;
2423
- break;
2424
- }
2425
- }
2426
- }
2427
- if (!hasContentLength) {
2428
- if (typeof body === "string") {
2429
- header["Content-Length"] = Buffer.byteLength(body);
2430
- } else if (body instanceof Uint8Array) {
2431
- header["Content-Length"] = body.byteLength;
2432
- } else if (body instanceof Blob) {
2433
- header["Content-Length"] = body.size;
2434
- }
2435
- }
2436
- outgoing.writeHead(status, header);
2437
- if (typeof body === "string" || body instanceof Uint8Array) {
2438
- outgoing.end(body);
2439
- } else if (body instanceof Blob) {
2440
- outgoing.end(new Uint8Array(await body.arrayBuffer()));
2441
- } else {
2442
- flushHeaders(outgoing);
2443
- await writeFromReadableStream(body, outgoing)?.catch(
2444
- (e) => handleResponseError(e, outgoing)
2445
- );
2446
- }
2447
- ;
2448
- outgoing[outgoingEnded]?.();
2449
- };
2450
- var isPromise = (res) => typeof res.then === "function";
2451
- var responseViaResponseObject = async (res, outgoing, options = {}) => {
2452
- if (isPromise(res)) {
2453
- if (options.errorHandler) {
2454
- try {
2455
- res = await res;
2456
- } catch (err) {
2457
- const errRes = await options.errorHandler(err);
2458
- if (!errRes) {
2459
- return;
2460
- }
2461
- res = errRes;
2462
- }
2463
- } else {
2464
- res = await res.catch(handleFetchError);
2465
- }
2466
- }
2467
- if (cacheKey in res) {
2468
- return responseViaCache(res, outgoing);
2469
- }
2470
- const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
2471
- if (res.body) {
2472
- const reader = res.body.getReader();
2473
- const values = [];
2474
- let done = false;
2475
- let currentReadPromise = void 0;
2476
- if (resHeaderRecord["transfer-encoding"] !== "chunked") {
2477
- let maxReadCount = 2;
2478
- for (let i = 0; i < maxReadCount; i++) {
2479
- currentReadPromise ||= reader.read();
2480
- const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
2481
- console.error(e);
2482
- done = true;
2483
- });
2484
- if (!chunk) {
2485
- if (i === 1) {
2486
- await new Promise((resolve4) => setTimeout(resolve4));
2487
- maxReadCount = 3;
2488
- continue;
2489
- }
2490
- break;
2491
- }
2492
- currentReadPromise = void 0;
2493
- if (chunk.value) {
2494
- values.push(chunk.value);
2495
- }
2496
- if (chunk.done) {
2497
- done = true;
2498
- break;
2499
- }
2500
- }
2501
- if (done && !("content-length" in resHeaderRecord)) {
2502
- resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
2503
- }
2504
- }
2505
- outgoing.writeHead(res.status, resHeaderRecord);
2506
- values.forEach((value) => {
2507
- ;
2508
- outgoing.write(value);
2509
- });
2510
- if (done) {
2511
- outgoing.end();
2512
- } else {
2513
- if (values.length === 0) {
2514
- flushHeaders(outgoing);
2515
- }
2516
- await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
2517
- }
2518
- } else if (resHeaderRecord[X_ALREADY_SENT]) {
2519
- } else {
2520
- outgoing.writeHead(res.status, resHeaderRecord);
2521
- outgoing.end();
2522
- }
2523
- ;
2524
- outgoing[outgoingEnded]?.();
2525
- };
2526
- var getRequestListener = (fetchCallback, options = {}) => {
2527
- const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
2528
- if (options.overrideGlobalObjects !== false && global.Request !== Request2) {
2529
- Object.defineProperty(global, "Request", {
2530
- value: Request2
2531
- });
2532
- Object.defineProperty(global, "Response", {
2533
- value: Response2
2534
- });
2535
- }
2536
- return async (incoming, outgoing) => {
2537
- let res, req;
2538
- try {
2539
- req = newRequest(incoming, options.hostname);
2540
- let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
2541
- if (!incomingEnded) {
2542
- ;
2543
- incoming[wrapBodyStream] = true;
2544
- incoming.on("end", () => {
2545
- incomingEnded = true;
2546
- });
2547
- if (incoming instanceof Http2ServerRequest2) {
2548
- ;
2549
- outgoing[outgoingEnded] = () => {
2550
- if (!incomingEnded) {
2551
- setTimeout(() => {
2552
- if (!incomingEnded) {
2553
- setTimeout(() => {
2554
- incoming.destroy();
2555
- outgoing.destroy();
2556
- });
2557
- }
2558
- });
2559
- }
2560
- };
2561
- }
2562
- }
2563
- outgoing.on("close", () => {
2564
- const abortController = req[abortControllerKey];
2565
- if (abortController) {
2566
- if (incoming.errored) {
2567
- req[abortControllerKey].abort(incoming.errored.toString());
2568
- } else if (!outgoing.writableFinished) {
2569
- req[abortControllerKey].abort("Client connection prematurely closed.");
2570
- }
2571
- }
2572
- if (!incomingEnded) {
2573
- setTimeout(() => {
2574
- if (!incomingEnded) {
2575
- setTimeout(() => {
2576
- incoming.destroy();
2577
- });
2578
- }
2579
- });
2580
- }
2581
- });
2582
- res = fetchCallback(req, { incoming, outgoing });
2583
- if (cacheKey in res) {
2584
- return responseViaCache(res, outgoing);
2585
- }
2586
- } catch (e) {
2587
- if (!res) {
2588
- if (options.errorHandler) {
2589
- res = await options.errorHandler(req ? e : toRequestError(e));
2590
- if (!res) {
2591
- return;
2592
- }
2593
- } else if (!req) {
2594
- res = handleRequestError();
2595
- } else {
2596
- res = handleFetchError(e);
2597
- }
2598
- } else {
2599
- return handleResponseError(e, outgoing);
2600
- }
2601
- }
2602
- try {
2603
- return await responseViaResponseObject(res, outgoing, options);
2604
- } catch (e) {
2605
- return handleResponseError(e, outgoing);
2606
- }
2607
- };
2608
- };
2609
- var createAdaptorServer = (options) => {
2610
- const fetchCallback = options.fetch;
2611
- const requestListener = getRequestListener(fetchCallback, {
2612
- hostname: options.hostname,
2613
- overrideGlobalObjects: options.overrideGlobalObjects,
2614
- autoCleanupIncoming: options.autoCleanupIncoming
2615
- });
2616
- const createServer = options.createServer || createServerHTTP;
2617
- const server = createServer(options.serverOptions || {}, requestListener);
2618
- return server;
2619
- };
2620
- var serve = (options, listeningListener) => {
2621
- const server = createAdaptorServer(options);
2622
- server.listen(options?.port ?? 3e3, options.hostname, () => {
2623
- const serverInfo = server.address();
2624
- listeningListener && listeningListener(serverInfo);
2625
- });
2626
- return server;
2627
- };
2628
-
2629
- // app/lib/paths.ts
2630
- import { homedir } from "os";
2631
- import { resolve, join } from "path";
2632
- import { existsSync, readFileSync } from "fs";
2633
- var configDirName = ".maxy";
2634
- var commercialMode = false;
2635
- var vncDisplayNum = 99;
2636
- var rfbPortNum = 5900;
2637
- var websockifyPortNum = 6080;
2638
- var cdpPortNum = 9222;
2639
- var portSource = "dev-default";
2640
- var platformRoot = process.env.MAXY_PLATFORM_ROOT;
2641
- if (platformRoot) {
2642
- const brandPath = join(platformRoot, "config", "brand.json");
2643
- if (existsSync(brandPath)) {
2644
- let brand;
2645
- try {
2646
- brand = JSON.parse(readFileSync(brandPath, "utf-8"));
2647
- } catch (err) {
2648
- const detail = (err instanceof Error ? err.message : String(err)).slice(0, 160);
2649
- console.error(`[paths] error reason=brand-config-missing path=${brandPath} detail="parse failed: ${detail.replace(/"/g, "'")}"`);
2650
- throw err;
2651
- }
2652
- if (typeof brand.configDir === "string") configDirName = brand.configDir;
2653
- if (brand.commercialMode === true) commercialMode = true;
2654
- if (typeof brand.vncDisplay === "number") vncDisplayNum = brand.vncDisplay;
2655
- const brandLabel = configDirName.replace(/^\./, "");
2656
- const required = [
2657
- ["rfbPort", brand.rfbPort],
2658
- ["websockifyPort", brand.websockifyPort],
2659
- ["cdpPort", brand.cdpPort]
2660
- ];
2661
- for (const [field, value] of required) {
2662
- if (typeof value !== "number") {
2663
- const keys = Object.keys(brand).join(",");
2664
- console.error(`[paths] error reason=cdp-port-unresolved brand=${brandLabel} path=${brandPath} field=${field} json_keys=${keys}`);
2665
- throw new Error(`brand.json at ${brandPath} missing required field: ${field}`);
2666
- }
2667
- }
2668
- rfbPortNum = brand.rfbPort;
2669
- websockifyPortNum = brand.websockifyPort;
2670
- cdpPortNum = brand.cdpPort;
2671
- portSource = "brand.json";
2672
- }
2673
- }
2674
- var MAXY_DIR = resolve(homedir(), configDirName);
2675
- var BRAND_NAME = configDirName.replace(/^\./, "");
2676
- var COMMERCIAL_MODE = commercialMode;
2677
- var VNC_DISPLAY = `:${vncDisplayNum}`;
2678
- var RFB_PORT = rfbPortNum;
2679
- var WEBSOCKIFY_PORT = websockifyPortNum;
2680
- var CDP_PORT = cdpPortNum;
2681
- console.log(
2682
- `[paths] brand=${configDirName.replace(/^\./, "")} vncDisplay=${vncDisplayNum} rfbPort=${RFB_PORT} websockifyPort=${WEBSOCKIFY_PORT} cdpPort=${CDP_PORT} source=${portSource}`
2683
- );
2684
- var CHROMIUM_PROFILE_DIR = resolve(homedir(), configDirName, "chromium-profile");
2685
- var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
2686
- var USERS_FILE = resolve(MAXY_DIR, "users.json");
2687
- var LOG_DIR = resolve(MAXY_DIR, "logs");
2688
- var BIN_DIR = resolve(MAXY_DIR, "bin");
2689
- var REMOTE_PASSWORD_FILE = resolve(MAXY_DIR, ".remote-password");
2690
- var REMOTE_SESSION_SECRET_FILE = resolve(MAXY_DIR, "credentials", "remote-session-secret");
2691
- var ADMIN_SESSION_SECRET_FILE = resolve(MAXY_DIR, "credentials", "admin-session-secret");
2692
- var TELEGRAM_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-webhook-secret");
2693
- var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-admin-webhook-secret");
2694
- var CLAUDE_CREDENTIALS_FILE = resolve(MAXY_DIR, ".claude", ".credentials.json");
2695
-
2696
- // app/lib/vnc-logger.ts
2697
- import { appendFileSync, mkdirSync } from "fs";
2698
- import { resolve as resolve2 } from "path";
2699
- var VNC_LOG_FILE = resolve2(LOG_DIR, "vnc-boot.log");
2700
- try {
2701
- mkdirSync(LOG_DIR, { recursive: true });
2702
- } catch (err) {
2703
- console.error(`[vnc-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
2704
- }
2705
- function vncLog(phase, fields = {}) {
2706
- const ts = (/* @__PURE__ */ new Date()).toISOString();
2707
- const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
2708
- const line = kv.length > 0 ? `[${ts}] [${phase}] ${kv}
2709
- ` : `[${ts}] [${phase}]
2710
- `;
2711
- try {
2712
- appendFileSync(VNC_LOG_FILE, line);
2713
- } catch (err) {
2714
- console.error(`[vnc-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
2715
- }
2716
- }
2717
- var corrCounter = 0;
2718
- function newCorrId() {
2719
- corrCounter = corrCounter + 1 & 4294967295;
2720
- const counterHex = corrCounter.toString(16).padStart(8, "0");
2721
- const randomHex = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
2722
- return `${counterHex}-${randomHex}`;
2723
- }
2724
- function sanitizeClientCorrId(raw2) {
2725
- if (!raw2) return null;
2726
- if (raw2.length > 32) return null;
2727
- if (!/^[A-Za-z0-9_-]+$/.test(raw2)) return null;
2728
- return raw2;
2729
- }
2730
- function formatKv(fields) {
2731
- return Object.entries(fields).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
2732
- }
2733
- function httpLog(fields) {
2734
- console.error(`[http] ${formatKv(fields)}`);
2735
- }
2736
- function websockifyLog(event, fields = {}) {
2737
- console.error(`[websockify] ${formatKv({ event, ...fields })}`);
2738
- }
2739
- function browserViewerLog(event, fields = {}) {
2740
- console.error(`[browser-viewer] ${formatKv({ event, ...fields })}`);
2741
- }
2742
-
2743
- // app/lib/password-strength.ts
2744
- function validatePasswordStrength(password) {
2745
- return [
2746
- { key: "length", label: "At least 8 characters", met: password.length >= 8 },
2747
- { key: "number", label: "Contains a number", met: /\d/.test(password) },
2748
- { key: "special", label: "Contains a special character", met: /[^A-Za-z0-9]/.test(password) },
2749
- { key: "whitespace", label: "No spaces", met: password.length > 0 && !/\s/.test(password) }
2750
- ];
2751
- }
2752
- function isPasswordValid(password) {
2753
- return validatePasswordStrength(password).every((r) => r.met);
2754
- }
2755
-
2756
- // app/lib/remote-auth.ts
2757
- import { scrypt, randomBytes, timingSafeEqual, createHmac } from "crypto";
2758
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
2759
- import { dirname } from "path";
2760
- var SCRYPT_N = 16384;
2761
- var SCRYPT_R = 8;
2762
- var SCRYPT_P = 1;
2763
- var SCRYPT_KEYLEN = 64;
2764
- function scryptAsync(password, salt) {
2765
- return new Promise((resolve4, reject) => {
2766
- scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
2767
- if (err) reject(err);
2768
- else resolve4(key);
2769
- });
2770
- });
2771
- }
2772
- async function hashPassword(password) {
2773
- const salt = randomBytes(32);
2774
- const hash = await scryptAsync(password, salt);
2775
- return `${salt.toString("hex")}:${hash.toString("hex")}`;
2776
- }
2777
- async function verifyPassword(password, stored) {
2778
- const colonIndex = stored.indexOf(":");
2779
- if (colonIndex === -1) return { ok: false, why: "bad-hash-format", hashBytes: 0 };
2780
- const saltHex = stored.slice(0, colonIndex);
2781
- const hashHex = stored.slice(colonIndex + 1);
2782
- if (!saltHex || !hashHex) return { ok: false, why: "bad-hash-format", hashBytes: 0 };
2783
- const salt = Buffer.from(saltHex, "hex");
2784
- const storedHash = Buffer.from(hashHex, "hex");
2785
- if (storedHash.length !== SCRYPT_KEYLEN) {
2786
- return { ok: false, why: "hash-length", hashBytes: storedHash.length };
2787
- }
2788
- let computed;
2789
- try {
2790
- computed = await scryptAsync(password, salt);
2791
- } catch {
2792
- return { ok: false, why: "scrypt-throw", hashBytes: storedHash.length };
2793
- }
2794
- if (timingSafeEqual(computed, storedHash)) {
2795
- return { ok: true, hashBytes: storedHash.length };
2796
- }
2797
- return { ok: false, why: "password-mismatch", hashBytes: storedHash.length };
2798
- }
2799
- function isRemoteAuthConfigured() {
2800
- if (!existsSync2(REMOTE_PASSWORD_FILE)) return false;
2801
- const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
2802
- return content.length > 0 && content.includes(":");
2803
- }
2804
- function readPasswordHash() {
2805
- try {
2806
- if (!existsSync2(REMOTE_PASSWORD_FILE)) return null;
2807
- const content = readFileSync2(REMOTE_PASSWORD_FILE, "utf-8").trim();
2808
- if (!content || !content.includes(":")) return null;
2809
- return content;
2810
- } catch {
2811
- return null;
2812
- }
2813
- }
2814
- async function setRemotePassword(password) {
2815
- const hash = await hashPassword(password);
2816
- writeFileSync(REMOTE_PASSWORD_FILE, hash, "utf-8");
2817
- }
2818
- async function verifyRemotePassword(password) {
2819
- const stored = readPasswordHash();
2820
- if (!stored) return { ok: false, why: "no-password-file", hashBytes: 0 };
2821
- return verifyPassword(password, stored);
2822
- }
2823
- var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
2824
- var TOKEN_PREFIX = "v1.";
2825
- var SECRET_BYTES = 32;
2826
- var NONCE_BYTES = 16;
2827
- var cachedSecret = null;
2828
- function getSecret() {
2829
- if (cachedSecret) return cachedSecret;
2830
- try {
2831
- const hex2 = readFileSync2(REMOTE_SESSION_SECRET_FILE, "utf-8").trim();
2832
- if (hex2.length === SECRET_BYTES * 2) {
2833
- cachedSecret = Buffer.from(hex2, "hex");
2834
- return cachedSecret;
2835
- }
2836
- } catch {
2837
- }
2838
- const fresh = randomBytes(SECRET_BYTES).toString("hex");
2839
- try {
2840
- mkdirSync2(dirname(REMOTE_SESSION_SECRET_FILE), { recursive: true, mode: 448 });
2841
- writeFileSync(REMOTE_SESSION_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
2842
- } catch {
2843
- }
2844
- const hex = readFileSync2(REMOTE_SESSION_SECRET_FILE, "utf-8").trim();
2845
- cachedSecret = Buffer.from(hex, "hex");
2846
- return cachedSecret;
2847
- }
2848
- function base64urlEncode(buf) {
2849
- return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2850
- }
2851
- function base64urlDecode(s) {
2852
- const padded = s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4);
2853
- return Buffer.from(padded, "base64");
2854
- }
2855
- function signPayload(payloadJson, secret) {
2856
- return base64urlEncode(createHmac("sha256", secret).update(payloadJson).digest());
2857
- }
2858
- function createRemoteSession(opts = {}) {
2859
- const payload = { c: opts.now ?? Date.now(), n: randomBytes(NONCE_BYTES).toString("hex") };
2860
- const payloadJson = JSON.stringify(payload);
2861
- const payloadB64 = base64urlEncode(Buffer.from(payloadJson, "utf-8"));
2862
- const sig = signPayload(payloadJson, getSecret());
2863
- return `${TOKEN_PREFIX}${payloadB64}.${sig}`;
2864
- }
2865
- function parseToken(token) {
2866
- if (!token.startsWith(TOKEN_PREFIX)) return null;
2867
- const rest = token.slice(TOKEN_PREFIX.length);
2868
- const dot = rest.indexOf(".");
2869
- if (dot === -1) return null;
2870
- const payloadB64 = rest.slice(0, dot);
2871
- const sigB64 = rest.slice(dot + 1);
2872
- if (!payloadB64 || !sigB64) return null;
2873
- let payloadJson;
2874
- try {
2875
- payloadJson = base64urlDecode(payloadB64).toString("utf-8");
2876
- } catch {
2877
- return null;
2878
- }
2879
- const expected = signPayload(payloadJson, getSecret());
2880
- const expectedBuf = Buffer.from(expected, "utf-8");
2881
- const provBuf = Buffer.from(sigB64, "utf-8");
2882
- const signatureValid = expectedBuf.length === provBuf.length && timingSafeEqual(expectedBuf, provBuf);
2883
- let payload;
2884
- try {
2885
- payload = JSON.parse(payloadJson);
2886
- } catch {
2887
- return null;
2888
- }
2889
- if (!payload || typeof payload !== "object" || typeof payload.c !== "number" || typeof payload.n !== "string") {
2890
- return null;
2891
- }
2892
- return { payload, signatureValid };
2893
- }
2894
- function validateRemoteSession(token, now = Date.now()) {
2895
- if (!token) return false;
2896
- const parsed = parseToken(token);
2897
- if (!parsed || !parsed.signatureValid) return false;
2898
- return now - parsed.payload.c <= SESSION_TTL_MS;
2899
- }
2900
- function describeRemoteSession(token, now = Date.now()) {
2901
- if (!token) return { present: false, expired: false };
2902
- const parsed = parseToken(token);
2903
- if (!parsed || !parsed.signatureValid) return { present: false, expired: false };
2904
- return { present: true, expired: now - parsed.payload.c > SESSION_TTL_MS };
2905
- }
2906
- function normalizeIp(ip) {
2907
- if (!ip) return void 0;
2908
- const trimmed = ip.trim();
2909
- if (trimmed.startsWith("::ffff:")) return trimmed.slice(7);
2910
- return trimmed;
2911
- }
2912
- function isLoopback(ip) {
2913
- return ip === "127.0.0.1" || ip.startsWith("127.") || ip === "::1";
2914
- }
2915
- function isPrivateIp(ip) {
2916
- const parts = ip.split(".");
2917
- if (parts.length !== 4) return false;
2918
- const a = parseInt(parts[0], 10);
2919
- const b = parseInt(parts[1], 10);
2920
- if (Number.isNaN(a) || Number.isNaN(b)) return false;
2921
- if (a === 10) return true;
2922
- if (a === 172 && b >= 16 && b <= 31) return true;
2923
- if (a === 192 && b === 168) return true;
2924
- if (a === 169 && b === 254) return true;
2925
- return false;
2926
- }
2927
- function classifyNormalizedIp(ip) {
2928
- if (isLoopback(ip) || isPrivateIp(ip)) return "lan";
2929
- return "external";
2930
- }
2931
- function resolveClientIp(remoteAddress, xForwardedFor) {
2932
- const remote = normalizeIp(remoteAddress);
2933
- if (!remote) return { ip: "", kind: "unknown" };
2934
- if (isLoopback(remote) && xForwardedFor) {
2935
- const firstIp = normalizeIp(xForwardedFor.split(",")[0]?.trim());
2936
- if (firstIp) {
2937
- return { ip: firstIp, kind: classifyNormalizedIp(firstIp) };
2938
- }
2939
- }
2940
- return { ip: remote, kind: classifyNormalizedIp(remote) };
2941
- }
2942
- var MAX_ATTEMPTS = 5;
2943
- var LOCKOUT_MS = 15 * 60 * 1e3;
2944
- var LOCAL_BUCKET = "__local__";
2945
- var rateLimitMap = /* @__PURE__ */ new Map();
2946
- function bucketKey(client) {
2947
- return client.kind === "external" ? client.ip : LOCAL_BUCKET;
2948
- }
2949
- function checkRateLimit(client) {
2950
- const key = bucketKey(client);
2951
- const rec = rateLimitMap.get(key);
2952
- if (!rec) return null;
2953
- if (rec.lockedUntil > Date.now()) {
2954
- const remaining = Math.ceil((rec.lockedUntil - Date.now()) / 1e3);
2955
- return `Too many attempts. Try again in ${remaining}s`;
2956
- }
2957
- if (rec.lockedUntil > 0) {
2958
- rateLimitMap.delete(key);
2959
- }
2960
- return null;
2961
- }
2962
- function recordFailedAttempt(client) {
2963
- const key = bucketKey(client);
2964
- const rec = rateLimitMap.get(key) ?? { count: 0, lockedUntil: 0 };
2965
- rec.count++;
2966
- if (rec.count >= MAX_ATTEMPTS) {
2967
- rec.lockedUntil = Date.now() + LOCKOUT_MS;
2968
- rec.count = 0;
2969
- }
2970
- rateLimitMap.set(key, rec);
2971
- }
2972
- function clearRateLimit(client) {
2973
- rateLimitMap.delete(bucketKey(client));
2974
- }
2975
- setInterval(() => {
2976
- const now = Date.now();
2977
- for (const [key, rec] of rateLimitMap) {
2978
- if (rec.lockedUntil > 0 && rec.lockedUntil <= now) {
2979
- rateLimitMap.delete(key);
2980
- }
2981
- }
2982
- }, 15 * 60 * 1e3).unref();
2983
- function renderLoginPage(opts) {
2984
- const error = opts?.error ?? "";
2985
- const lockout = opts?.lockoutSeconds ?? 0;
2986
- const redirect = opts?.redirect ?? "/";
2987
- const mode = opts?.mode ?? "login";
2988
- const changeError = opts?.changeError ?? "";
2989
- const success = opts?.success ?? "";
2990
- const setupError = opts?.setupError ?? "";
2991
- const primaryColor = opts?.primaryColor ?? "#7C8C72";
2992
- const primaryHoverColor = opts?.primaryHoverColor ?? "#6A7A62";
2993
- const primarySubtle = opts?.primarySubtle ?? "rgba(124,140,114,0.08)";
2994
- const productName = opts?.productName ?? "Maxy";
2995
- const logoPath = opts?.logoPath ?? "/brand/maxy-monochrome.png";
2996
- const faviconPath = opts?.faviconPath ?? "/favicon.ico";
2997
- const displayFont = opts?.displayFont ?? "'Cormorant', Georgia, serif";
2998
- const bodyFont = opts?.bodyFont ?? "'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
2999
- const logoContainsName = opts?.logoContainsName ?? false;
3000
- const errorHtml = error ? `<p class="msg msg--error">${escapeHtml(error)}</p>` : "";
3001
- const changeErrorHtml = changeError ? `<p class="msg msg--error">${escapeHtml(changeError)}</p>` : "";
3002
- const successHtml = success ? `<p class="msg msg--success">${escapeHtml(success)}</p>` : "";
3003
- const lockoutHtml = lockout > 0 ? `<p class="msg msg--error">Too many attempts. Try again in <span id="countdown">${lockout}</span>s</p>
3004
- <script>
3005
- (function() {
3006
- var s = ${lockout};
3007
- var el = document.getElementById('countdown');
3008
- var iv = setInterval(function() { s--; el.textContent = s; if (s <= 0) { clearInterval(iv); location.reload(); } }, 1000);
3009
- })();
3010
- </script>` : "";
3011
- const setupErrorHtml = setupError ? `<p class="msg msg--error">${escapeHtml(setupError)}</p>` : "";
3012
- const formDisabled = lockout > 0 ? "disabled" : "";
3013
- const loginDisplay = mode === "login" ? "block" : "none";
3014
- const changeDisplay = mode === "change" ? "block" : "none";
3015
- const setupDisplay = mode === "setup" ? "block" : "none";
3016
- const successDisplay = mode === "success" ? "block" : "none";
3017
- const subtitleText = mode === "setup" ? "Set your remote password" : "Remote access";
3018
- const displayFontName = displayFont.match(/^'([^']+)'/)?.[1] ?? "";
3019
- const bodyFontName = bodyFont.match(/^'([^']+)'/)?.[1] ?? "";
3020
- const googleFontsLink = displayFontName || bodyFontName ? [
3021
- '<link rel="preconnect" href="https://fonts.googleapis.com">',
3022
- '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',
3023
- '<link href="https://fonts.googleapis.com/css2?' + [
3024
- displayFontName ? `family=${displayFontName.replace(/ /g, "+")}:wght@300;400` : "",
3025
- bodyFontName ? `family=${bodyFontName.replace(/ /g, "+")}:wght@400;500` : ""
3026
- ].filter(Boolean).join("&") + '&display=swap" rel="stylesheet">'
3027
- ].join("\n ") : "";
3028
- return `<!DOCTYPE html>
3029
- <html lang="en">
3030
- <head>
3031
- <meta charset="utf-8">
3032
- <meta name="viewport" content="width=device-width, initial-scale=1">
3033
- <title>Sign in \u2014 ${escapeHtml(productName)}</title>
3034
- <link rel="icon" href="${escapeHtml(faviconPath)}">
3035
- ${googleFontsLink}
3036
- <style>
3037
- * { margin: 0; padding: 0; box-sizing: border-box; }
3038
- body {
3039
- font-family: ${bodyFont};
3040
- background: #FAFAF8;
3041
- color: #1A1A1A;
3042
- min-height: 100vh;
3043
- display: flex;
3044
- align-items: center;
3045
- justify-content: center;
3046
- }
3047
- .page {
3048
- display: flex;
3049
- flex-direction: column;
3050
- align-items: center;
3051
- gap: 24px;
3052
- max-width: 420px;
3053
- width: 100%;
3054
- padding: 40px 20px;
3055
- }
3056
- .logo-img {
3057
- width: 110px;
3058
- height: 110px;
3059
- margin: -11px;
3060
- object-fit: contain;
3061
- }
3062
- .title {
3063
- font-family: ${displayFont};
3064
- font-weight: 300;
3065
- font-size: 28px;
3066
- color: #1A1A1A;
3067
- text-align: center;
3068
- letter-spacing: -0.01em;
3069
- line-height: 1.3;
3070
- }
3071
- .subtitle {
3072
- font-size: 15px;
3073
- color: #6B6B6B;
3074
- text-align: center;
3075
- margin-top: -8px;
3076
- }
3077
- .form-wrap {
3078
- width: 100%;
3079
- max-width: 300px;
3080
- }
3081
- .field { margin-bottom: 12px; }
3082
- .field-label {
3083
- display: block;
3084
- font-size: 13px;
3085
- font-weight: 500;
3086
- color: #6B6B6B;
3087
- margin-bottom: 6px;
3088
- }
3089
- .field-input {
3090
- width: 100%;
3091
- padding: 10px 12px;
3092
- border: 1px solid rgba(0,0,0,0.1);
3093
- border-radius: 8px;
3094
- font-size: 15px;
3095
- font-family: inherit;
3096
- outline: none;
3097
- background: #fff;
3098
- transition: border-color 0.15s, box-shadow 0.15s;
3099
- }
3100
- .field-input:focus {
3101
- border-color: ${primaryColor};
3102
- box-shadow: 0 0 0 3px ${primarySubtle};
3103
- }
3104
- .options {
3105
- display: flex;
3106
- align-items: center;
3107
- justify-content: space-between;
3108
- gap: 12px;
3109
- margin-top: 8px;
3110
- }
3111
-
3112
- /* Checkbox \u2014 matches Maxy shared checkbox (asterisk-in-square) */
3113
- .check {
3114
- position: relative;
3115
- display: flex;
3116
- align-items: center;
3117
- gap: 8px;
3118
- cursor: pointer;
3119
- user-select: none;
3120
- }
3121
- .check input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
3122
- .check-box {
3123
- width: 14px;
3124
- height: 14px;
3125
- border: 1px solid rgba(0,0,0,0.1);
3126
- border-radius: 2px;
3127
- display: flex;
3128
- align-items: center;
3129
- justify-content: center;
3130
- font-size: 10px;
3131
- color: transparent;
3132
- transition: border-color 0.15s, color 0.15s;
3133
- flex-shrink: 0;
3134
- }
3135
- .check input:checked + .check-box {
3136
- border-color: ${primaryColor};
3137
- color: ${primaryColor};
3138
- }
3139
- .check-label {
3140
- font-size: 13px;
3141
- color: #6B6B6B;
3142
- }
3143
-
3144
- /* Ghost button \u2014 matches Maxy ghost variant */
3145
- .ghost {
3146
- background: none;
3147
- border: none;
3148
- font-family: inherit;
3149
- font-size: 13px;
3150
- color: #6B6B6B;
3151
- cursor: pointer;
3152
- padding: 4px 0;
3153
- transition: color 0.15s;
3154
- }
3155
- .ghost:hover { color: #1A1A1A; }
3156
-
3157
- /* Primary button */
3158
- .btn {
3159
- width: 100%;
3160
- padding: 12px;
3161
- margin-top: 16px;
3162
- background: ${primaryColor};
3163
- color: #fff;
3164
- border: none;
3165
- border-radius: 10px;
3166
- font-size: 15px;
3167
- font-weight: 500;
3168
- font-family: inherit;
3169
- cursor: pointer;
3170
- transition: background 0.15s;
3171
- }
3172
- .btn:hover:not(:disabled) { background: ${primaryHoverColor}; }
3173
- .btn:disabled { opacity: 0.5; cursor: not-allowed; }
3174
-
3175
- /* Messages */
3176
- .msg {
3177
- font-size: 13px;
3178
- text-align: center;
3179
- margin-top: 8px;
3180
- }
3181
- .msg--error { color: #c44; }
3182
- .msg--success { color: ${primaryColor}; }
3183
-
3184
- /* Strength checklist (setup mode) */
3185
- .strength-checklist { margin: 8px 0; }
3186
- .strength-item {
3187
- font-size: 13px;
3188
- color: #6B6B6B;
3189
- padding: 2px 0;
3190
- transition: color 0.15s;
3191
- }
3192
- .strength-icon {
3193
- display: inline-block;
3194
- width: 16px;
3195
- text-align: center;
3196
- }
3197
-
3198
- @media (max-width: 440px) {
3199
- .page { padding: 24px 16px; }
3200
- }
3201
- </style>
3202
- </head>
3203
- <body>
3204
- <div class="page">
3205
- <img src="${escapeHtml(logoPath)}" alt="${escapeHtml(productName)}" class="logo-img">
3206
- <h1 class="title"${logoContainsName ? ' style="display:none"' : ""}>${escapeHtml(productName)}</h1>
3207
- <p class="subtitle">${escapeHtml(subtitleText)}</p>
3208
-
3209
- ${successHtml}
3210
-
3211
- <!-- Login -->
3212
- <div id="login-section" class="form-wrap" style="display:${loginDisplay}">
3213
- <form method="POST" action="/__remote-auth/login">
3214
- <input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
3215
- <div class="field">
3216
- <label class="field-label" for="password">Password</label>
3217
- <input class="field-input" type="password" id="password" name="password" required autofocus ${formDisabled}>
3218
- </div>
3219
- <div class="options">
3220
- <label class="check">
3221
- <input type="checkbox" id="show-login-pw">
3222
- <span class="check-box">\u2731</span>
3223
- <span class="check-label">Show password</span>
3224
- </label>
3225
- <button type="button" class="ghost" onclick="toggleMode('change')">Change password</button>
3226
- </div>
3227
- <button type="submit" class="btn" ${formDisabled}>Sign in</button>
3228
- </form>
3229
- ${errorHtml}
3230
- ${lockoutHtml}
3231
- </div>
3232
-
3233
- <!-- Change password -->
3234
- <div id="change-section" class="form-wrap" style="display:${changeDisplay}">
3235
- <form method="POST" action="/__remote-auth/change-password">
3236
- <input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
3237
- <div class="field">
3238
- <label class="field-label" for="current_password">Current password</label>
3239
- <input class="field-input" type="password" id="current_password" name="current_password" required ${formDisabled}>
3240
- </div>
3241
- <div class="field">
3242
- <label class="field-label" for="new_password">New password</label>
3243
- <input class="field-input" type="password" id="new_password" name="new_password" required ${formDisabled}>
3244
- </div>
3245
- <div class="field">
3246
- <label class="field-label" for="confirm_password">Confirm new password</label>
3247
- <input class="field-input" type="password" id="confirm_password" name="confirm_password" required ${formDisabled}>
3248
- </div>
3249
- <div class="options">
3250
- <label class="check">
3251
- <input type="checkbox" id="show-change-pw">
3252
- <span class="check-box">\u2731</span>
3253
- <span class="check-label">Show passwords</span>
3254
- </label>
3255
- <button type="button" class="ghost" onclick="toggleMode('login')">Back to sign in</button>
3256
- </div>
3257
- <button type="submit" class="btn" ${formDisabled}>Change password</button>
3258
- </form>
3259
- ${changeErrorHtml}
3260
- </div>
3261
-
3262
- <!-- Setup (initial password) -->
3263
- <div id="setup-section" class="form-wrap" style="display:${setupDisplay}">
3264
- <form method="POST" action="/__remote-auth/set-initial-password">
3265
- <div class="field">
3266
- <label class="field-label" for="setup_password">Password</label>
3267
- <input class="field-input" type="password" id="setup_password" name="password" required autofocus>
3268
- </div>
3269
- <div class="field">
3270
- <label class="field-label" for="setup_confirm">Confirm password</label>
3271
- <input class="field-input" type="password" id="setup_confirm" name="confirm_password" required>
3272
- </div>
3273
- <div id="strength-checklist" class="strength-checklist">
3274
- <div class="strength-item" id="req-length"><span class="strength-icon">\u25CB</span> At least 8 characters</div>
3275
- <div class="strength-item" id="req-number"><span class="strength-icon">\u25CB</span> Contains a number</div>
3276
- <div class="strength-item" id="req-special"><span class="strength-icon">\u25CB</span> Contains a special character</div>
3277
- <div class="strength-item" id="req-spaces"><span class="strength-icon">\u25CB</span> No spaces</div>
3278
- </div>
3279
- <div class="options">
3280
- <label class="check">
3281
- <input type="checkbox" id="show-setup-pw">
3282
- <span class="check-box">\u2731</span>
3283
- <span class="check-label">Show passwords</span>
3284
- </label>
3285
- </div>
3286
- <button type="submit" class="btn" id="setup-submit" disabled>Set password</button>
3287
- </form>
3288
- ${setupErrorHtml}
3289
- </div>
3290
-
3291
- <!-- Success (after initial password set) -->
3292
- <div id="success-section" class="form-wrap" style="display:${successDisplay}">
3293
- <p class="msg msg--success" style="font-size:15px;margin-bottom:4px;">\u2713 Password set</p>
3294
- <p style="font-size:14px;color:#6B6B6B;text-align:center;">You can close this tab and return to the onboarding tab.</p>
3295
- <script>
3296
- (function() {
3297
- try {
3298
- var ch = new BroadcastChannel('platform-onboarding');
3299
- setTimeout(function() {
3300
- ch.postMessage({ type: 'remote-password-set' });
3301
- ch.close();
3302
- }, 2500);
3303
- } catch(e) {}
3304
- })();
3305
- </script>
3306
- </div>
3307
- </div>
3308
-
3309
- <script>
3310
- /* Toggle between login and change-password modes */
3311
- function toggleMode(mode) {
3312
- document.getElementById('login-section').style.display = mode === 'login' ? 'block' : 'none';
3313
- document.getElementById('change-section').style.display = mode === 'change' ? 'block' : 'none';
3314
- /* Focus first input in the active section */
3315
- var id = mode === 'login' ? 'password' : 'current_password';
3316
- var el = document.getElementById(id);
3317
- if (el) el.focus();
3318
- }
3319
-
3320
- /* Show/hide password toggles */
3321
- function bindShowHide(checkboxId, inputIds) {
3322
- var cb = document.getElementById(checkboxId);
3323
- if (!cb) return;
3324
- cb.addEventListener('change', function() {
3325
- var type = cb.checked ? 'text' : 'password';
3326
- inputIds.forEach(function(id) {
3327
- var el = document.getElementById(id);
3328
- if (el) el.type = type;
3329
- });
3330
- });
3331
- }
3332
- bindShowHide('show-login-pw', ['password']);
3333
- bindShowHide('show-change-pw', ['current_password', 'new_password', 'confirm_password']);
3334
- bindShowHide('show-setup-pw', ['setup_password', 'setup_confirm']);
3335
-
3336
- /* Explicit Enter-to-submit for the login password field */
3337
- var loginPw = document.getElementById('password');
3338
- if (loginPw) {
3339
- loginPw.addEventListener('keydown', function(e) {
3340
- if (e.key === 'Enter') {
3341
- e.preventDefault();
3342
- this.closest('form').requestSubmit();
3343
- }
3344
- });
3345
- }
3346
-
3347
- /* Live password strength validation (setup mode) */
3348
- (function() {
3349
- var pw = document.getElementById('setup_password');
3350
- var confirm = document.getElementById('setup_confirm');
3351
- var btn = document.getElementById('setup-submit');
3352
- if (!pw || !confirm || !btn) return;
3353
-
3354
- var rules = [
3355
- { id: 'req-length', test: function(p) { return p.length >= 8; } },
3356
- { id: 'req-number', test: function(p) { return /\\d/.test(p); } },
3357
- { id: 'req-special', test: function(p) { return /[^A-Za-z0-9]/.test(p); } },
3358
- { id: 'req-spaces', test: function(p) { return p.length > 0 && !/\\s/.test(p); } }
3359
- ];
3360
-
3361
- function check() {
3362
- var p = pw.value;
3363
- var allMet = true;
3364
- rules.forEach(function(r) {
3365
- var el = document.getElementById(r.id);
3366
- var met = r.test(p);
3367
- if (!met) allMet = false;
3368
- el.querySelector('.strength-icon').textContent = met ? '\\u2713' : '\\u25CB';
3369
- el.style.color = met ? '${primaryColor}' : '#6B6B6B';
3370
- });
3371
- var match = p.length > 0 && p === confirm.value;
3372
- btn.disabled = !(allMet && match);
3373
- }
3374
-
3375
- pw.addEventListener('input', check);
3376
- confirm.addEventListener('input', check);
3377
- })();
3378
- </script>
3379
- </body>
3380
- </html>`;
3381
- }
3382
- function escapeHtml(str) {
3383
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3384
- }
3385
-
3386
- // app/lib/auth-gate.ts
3387
- function canAccessAdmin(input) {
3388
- const client = resolveClientIp(input.remoteAddress, input.xForwardedFor);
3389
- if (input.isPublicHost(input.host)) {
3390
- return { allow: false, reason: "public-host", client };
3391
- }
3392
- if (client.kind !== "external") {
3393
- return { allow: true, client };
3394
- }
3395
- if (!isRemoteAuthConfigured()) {
3396
- return { allow: false, reason: "remote-unconfigured", client };
3397
- }
3398
- const token = parseCookieValue(input.cookieHeader, "__remote_session");
3399
- if (token && validateRemoteSession(token)) {
3400
- return { allow: true, client };
3401
- }
3402
- return { allow: false, reason: "unauthorized", client };
3403
- }
3404
- function parseCookieValue(cookieHeader, name) {
3405
- if (!cookieHeader) return null;
3406
- const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
3407
- if (!match2) return null;
3408
- try {
3409
- return decodeURIComponent(match2[1]);
3410
- } catch {
3411
- return null;
3412
- }
3413
- }
3414
-
3415
- // app/lib/claude-agent.ts
3416
- var agentLogStream = (..._args) => null;
3417
- var clearSessionHistory = (..._args) => null;
3418
- var compactSession = (..._args) => null;
3419
- var completeGrantSetup = (..._args) => null;
3420
- var findMissingPlugins = (..._args) => null;
3421
- var fingerprintSessionKey = (..._args) => null;
3422
- var getAccountIdForSession = (..._args) => null;
3423
- var getAgentNameForSession = (..._args) => null;
3424
- var getConversationIdForSession = (..._args) => null;
3425
- var getDefaultAccountId = (..._args) => null;
3426
- var getGrantForSession = (..._args) => null;
3427
- var getGroupSlugForSession = (..._args) => null;
3428
- var getRoleForSession = (..._args) => null;
3429
- var getSessionMessages = (..._args) => null;
3430
- var getStreamLogHandle = (..._args) => null;
3431
- var getUserIdForSession = (..._args) => null;
3432
- var getUserNameForSession = (..._args) => null;
3433
- var getVisitorIdForSession = (..._args) => null;
3434
- var hasStubAccountDir = (..._args) => null;
3435
- var invokeAgent = (..._args) => null;
3436
- var listAdminSessionsInProgress = (..._args) => null;
3437
- var mintAdminSessionToken = (..._args) => null;
3438
- var registerGrantSession = (..._args) => null;
3439
- var registerResumedSession = (..._args) => null;
3440
- var registerSession = (..._args) => null;
3441
- var resolveAccount = (..._args) => null;
3442
- var resolveAgentConfig = (..._args) => null;
3443
- var resolveDefaultAgentSlug = (..._args) => null;
3444
- var resolveUserAccounts = (..._args) => null;
3445
- var setAgentSessionId = (..._args) => null;
3446
- var setConversationIdForSession = (..._args) => null;
3447
- var setGroupContextForSession = (..._args) => null;
3448
- var setWantsPriorConversation = (..._args) => null;
3449
- var sigtermFlushStreamLogs = (..._args) => null;
3450
- var streamLogPathFor = (..._args) => null;
3451
- var unregisterSession = (..._args) => null;
3452
- var validateAgentSlug = (..._args) => null;
3453
- var validateSession = (..._args) => null;
3454
-
3455
- // server/routes/_helpers.ts
3456
- async function safeJson(c) {
3457
- try {
3458
- return await c.req.json();
3459
- } catch {
3460
- return null;
3461
- }
3462
- }
3463
- async function resolveCacheKey(c) {
3464
- const raw2 = await resolveRawSessionKey(c);
3465
- if (!raw2) return { fingerprint: "", signedSessionToken: "" };
3466
- return { fingerprint: fingerprintSessionKey(raw2), signedSessionToken: raw2 };
3467
- }
3468
- async function resolveRawSessionKey(c) {
3469
- const fromQuery = c.req.query("session_key");
3470
- if (fromQuery) return fromQuery;
3471
- const contentType = c.req.header("content-type") ?? "";
3472
- if (contentType.includes("application/json")) {
3473
- try {
3474
- const body = await c.req.json();
3475
- if (body && typeof body.session_key === "string" && body.session_key) {
3476
- return body.session_key;
3477
- }
3478
- } catch {
3479
- }
3480
- }
3481
- if (contentType.includes("multipart/form-data") || contentType.includes("application/x-www-form-urlencoded")) {
3482
- try {
3483
- const form = await c.req.formData();
3484
- const sk = form.get("session_key");
3485
- if (typeof sk === "string" && sk) return sk;
3486
- } catch {
3487
- }
3488
- }
3489
- return "";
3490
- }
3491
- var requireAdminSession = async (c, next) => {
3492
- const { fingerprint, signedSessionToken } = await resolveCacheKey(c);
3493
- if (!fingerprint) {
3494
- console.error(`[admin-auth] outcome=reject status=400 code=session-missing path=${c.req.path}`);
3495
- return c.json({ error: "session_key required", code: "session-missing" }, 400);
3496
- }
3497
- const result = validateSession(fingerprint, "admin", signedSessionToken);
3498
- if (!result.ok) {
3499
- const code = result.reason === "agent-type-mismatch" ? "session-not-registered" : result.reason;
3500
- const tail = fingerprint.slice(0, 8);
3501
- console.error(`[admin-auth] outcome=reject status=401 code=${code} path=${c.req.path} cacheKey=${tail}\u2026`);
3502
- return c.json({ error: "Invalid or expired admin session", code }, 401);
3503
- }
3504
- c.set("cacheKey", fingerprint);
3505
- c.set("signedSessionToken", signedSessionToken);
3506
- console.log(`[admin-auth] outcome=accept path=${c.req.path} cacheKey=${fingerprint.slice(0, 8)}\u2026`);
3507
- await next();
3508
- };
3509
- async function tryCookieBridgeForConversation(c, cacheKey2, conversationId) {
3510
- const path = c.req.path;
3511
- const sk8 = cacheKey2.slice(0, 8);
3512
- const cid8 = conversationId.slice(0, 8);
3513
- const client = resolveClientIp(
3514
- c.env?.incoming?.socket?.remoteAddress,
3515
- c.req.header("x-forwarded-for")
3516
- );
3517
- if (client.kind === "external") {
3518
- const cookieToken = parseCookieValue(c.req.header("cookie"), "__remote_session");
3519
- if (!cookieToken) {
3520
- console.error(`[session] cookie-bridge-rejected reason=no-cookie path=${path} cacheKey=${sk8}\u2026 conversationId=${cid8}\u2026`);
3521
- return { ok: false, reason: "no-cookie" };
3522
- }
3523
- const desc = describeRemoteSession(cookieToken);
3524
- if (!desc.present) {
3525
- console.error(`[session] cookie-bridge-rejected reason=cookie-invalid path=${path} cacheKey=${sk8}\u2026 conversationId=${cid8}\u2026`);
3526
- return { ok: false, reason: "cookie-invalid" };
3527
- }
3528
- if (desc.expired) {
3529
- console.error(`[session] cookie-bridge-rejected reason=cookie-expired path=${path} cacheKey=${sk8}\u2026 conversationId=${cid8}\u2026`);
3530
- return { ok: false, reason: "cookie-expired" };
3531
- }
3532
- }
3533
- const owner = await getConversationOwner(conversationId);
3534
- if (!owner) {
3535
- console.error(`[session] cookie-bridge-rejected reason=conversation-not-found path=${path} cacheKey=${sk8}\u2026 conversationId=${cid8}\u2026`);
3536
- return { ok: false, reason: "conversation-not-found" };
3537
- }
3538
- const deviceAccount = resolveAccount();
3539
- if (!deviceAccount || owner.accountId !== deviceAccount.accountId) {
3540
- console.error(`[session] cookie-bridge-rejected reason=account-mismatch path=${path} cacheKey=${sk8}\u2026 conversationId=${cid8}\u2026 conversationAccountId=${owner.accountId.slice(0, 8)}\u2026 deviceAccountId=${(deviceAccount?.accountId ?? "none").slice(0, 8)}\u2026`);
3541
- return { ok: false, reason: "account-mismatch" };
3542
- }
3543
- registerSession(cacheKey2, "admin", owner.accountId, void 0, owner.userId ?? void 0);
3544
- console.log(`[session] cookie-bridge accountId=${owner.accountId.slice(0, 8)}\u2026 cacheKey=${sk8}\u2026 conversationId=${cid8}\u2026 path=${path}`);
3545
- return { ok: true };
3546
- }
3547
- function requireAdminCookie(isPublicHost) {
3548
- return async (c, next) => {
3549
- const host = (c.req.header("host") ?? "").split(":")[0];
3550
- const decision = canAccessAdmin({
3551
- host,
3552
- remoteAddress: c.env?.incoming?.socket?.remoteAddress,
3553
- xForwardedFor: c.req.header("x-forwarded-for"),
3554
- cookieHeader: c.req.header("cookie"),
3555
- isPublicHost
3556
- });
3557
- if (decision.allow) {
3558
- await next();
3559
- return;
3560
- }
3561
- if (decision.reason === "public-host") {
3562
- return c.json({ error: "Not found" }, 404);
3563
- }
3564
- const clientIp = c.var.clientIp ?? "unknown";
3565
- console.error(`[admin-cookie] reject reason=${decision.reason} ip=${clientIp} path=${c.req.path}`);
3566
- return c.json({ error: "Unauthorized" }, 401);
3567
- };
3568
- }
3569
- var clientIpMiddleware = async (c, next) => {
3570
- const client = resolveClientIp(
3571
- c.env?.incoming?.socket?.remoteAddress,
3572
- c.req.header("x-forwarded-for")
3573
- );
3574
- c.set("clientIp", client.ip);
3575
- await next();
3576
- };
3577
-
3578
- // server/lib/action-runner.ts
3579
- import { spawn } from "child_process";
3580
- import { createReadStream, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, statSync, unlinkSync, watch } from "fs";
3581
- import { homedir as homedir2 } from "os";
3582
- import { join as join2, resolve as resolve3 } from "path";
3583
- import { readFileSync as readFileSync3 } from "fs";
3584
- import { setTimeout as delay } from "timers/promises";
3585
- function loadBrandInfo() {
3586
- const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
3587
- const brandPath = resolve3(platformRoot2, "config", "brand.json");
3588
- try {
3589
- const parsed = JSON.parse(readFileSync3(brandPath, "utf-8"));
3590
- const hostname = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
3591
- const configDir = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
3592
- return { hostname, configDir };
3593
- } catch {
3594
- return { hostname: "maxy", configDir: ".maxy" };
3595
- }
3596
- }
3597
- function actionLogDir() {
3598
- const { configDir } = loadBrandInfo();
3599
- return join2(homedir2(), configDir, "logs", "actions");
3600
- }
3601
- function actionLogPath(actionId) {
3602
- return join2(actionLogDir(), `${actionId}.log`);
3603
- }
3604
- var WHITELIST = {
3605
- upgrade: {
3606
- build: ({ sudoPassword }) => {
3607
- const { hostname } = loadBrandInfo();
3608
- const pkg = `@rubytech/create-${hostname}`;
3609
- const cmd = [
3610
- // prime: read pw from stdin, silently cache
3611
- 'printf %s "$SUDO_PASSWORD"',
3612
- "|",
3613
- "sudo -S -v 2>/dev/null",
3614
- "&&",
3615
- // run installer — it will re-prompt only if the cache expires
3616
- `npx -y ${pkg}@latest`
3617
- ].join(" ");
3618
- return {
3619
- argv: ["bash", "-c", cmd],
3620
- env: sudoPassword ? { SUDO_PASSWORD: sudoPassword } : void 0
3621
- };
3622
- }
3623
- },
3624
- "cloudflare-setup": {
3625
- build: ({ positional, streamLogPath, accountDir, tunnelId, tunnelName }) => {
3626
- if (!positional || positional.length < 3) {
3627
- throw new Error("cloudflare-setup requires [brand, port, ...hostnames]");
3628
- }
3629
- if (!accountDir) {
3630
- throw new Error("cloudflare-setup requires accountDir");
3631
- }
3632
- const idSet = typeof tunnelId === "string" && tunnelId.length > 0;
3633
- const nameSet = typeof tunnelName === "string" && tunnelName.length > 0;
3634
- if (idSet && nameSet) {
3635
- throw new Error("cloudflare-setup: tunnelId and tunnelName are mutually exclusive \u2014 pass exactly one");
3636
- }
3637
- if (!idSet && !nameSet) {
3638
- throw new Error("cloudflare-setup: requires tunnelId (operator-selected) OR tunnelName (operator-created)");
3639
- }
3640
- const scriptPath = join2(homedir2(), "setup-tunnel.sh");
3641
- if (!existsSync3(scriptPath)) {
3642
- throw new Error(
3643
- `cloudflare-setup: ~/setup-tunnel.sh not found. Re-run the installer to repair the symlink.`
3644
- );
3645
- }
3646
- const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
3647
- return {
3648
- argv: [scriptPath, ...positional],
3649
- env: {
3650
- MAXY_PLATFORM_ROOT: platformRoot2,
3651
- ...streamLogPath ? { STREAM_LOG_PATH: streamLogPath } : {},
3652
- ACCOUNT_DIR: accountDir,
3653
- ...idSet ? { TUNNEL_ID: tunnelId } : {},
3654
- ...nameSet ? { TUNNEL_NAME: tunnelName } : {}
3655
- }
3656
- };
3657
- },
3658
- reconcileFromLog: reconcileCloudflareSetupFromLog
3659
- }
3660
- };
3661
- var ARMING_LINE_RE = /\bstep=service-restart-armed\b[^\n]*\bexit=0\b/;
3662
- var DONE_LINE_RE = /\bstep=done\b/;
3663
- var ERROR_LINE_RE = /\bresult=error\b/;
3664
- function reconcileCloudflareSetupFromLog(lines) {
3665
- let hasArming = false;
3666
- let hasDone = false;
3667
- let hasError = false;
3668
- for (const line of lines) {
3669
- if (!hasArming && ARMING_LINE_RE.test(line)) hasArming = true;
3670
- if (!hasDone && DONE_LINE_RE.test(line)) hasDone = true;
3671
- if (!hasError && ERROR_LINE_RE.test(line)) hasError = true;
3672
- }
3673
- if (hasDone && hasArming) return { kind: "succeeded", code: 0 };
3674
- if (hasError) return { kind: "failed", code: 1 };
3675
- if (hasArming && !hasDone) return { kind: "restart-in-progress", code: null };
3676
- return null;
3677
- }
3678
- function isKnownAction(name) {
3679
- return Object.prototype.hasOwnProperty.call(WHITELIST, name);
3680
- }
3681
- function generateActionId(name) {
3682
- const ts = Date.now().toString(36);
3683
- const nonce = Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
3684
- return `${name}-${ts}-${nonce}`;
3685
- }
3686
- function actionNameFromId(actionId) {
3687
- for (const name of Object.keys(WHITELIST)) {
3688
- if (actionId.startsWith(`${name}-`)) return name;
3689
- }
3690
- return "unknown";
3691
- }
3692
- function cleanupStaleLogs() {
3693
- const dir = actionLogDir();
3694
- if (!existsSync3(dir)) return;
3695
- const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
3696
- for (const entry of readdirSync(dir)) {
3697
- const full = join2(dir, entry);
3698
- try {
3699
- const st = statSync(full);
3700
- if (st.isFile() && st.mtimeMs < cutoff) unlinkSync(full);
3701
- } catch {
3702
- }
3703
- }
3704
- }
3705
- async function isActionActive(actionName) {
3706
- return new Promise((resolveP) => {
3707
- const proc = spawn(
3708
- "systemctl",
3709
- ["--user", "list-units", "--no-legend", "--state=active", `maxy-action-${actionName}-*`],
3710
- { stdio: ["ignore", "pipe", "pipe"] }
3711
- );
3712
- let out = "";
3713
- proc.stdout.on("data", (chunk) => {
3714
- out += chunk.toString();
3715
- });
3716
- proc.on("close", () => resolveP(out.trim().length > 0));
3717
- proc.on("error", () => resolveP(false));
3718
- });
3719
- }
3720
- function buildSystemdArgs(name, args, actionId, logPath) {
3721
- const unit = `maxy-action-${actionId}`;
3722
- const built = WHITELIST[name].build(args);
3723
- const systemdArgs = [
3724
- "--user",
3725
- `--unit=${unit}`,
3726
- "--collect",
3727
- "--property=RemainAfterExit=false",
3728
- `--property=StandardOutput=append:${logPath}`,
3729
- `--property=StandardError=append:${logPath}`,
3730
- `--description=Maxy action ${name} (${actionId})`
3731
- ];
3732
- if (built.env) {
3733
- for (const [k, v] of Object.entries(built.env)) {
3734
- systemdArgs.push(`--setenv=${k}=${v}`);
3735
- }
3736
- }
3737
- systemdArgs.push("--", ...built.argv);
3738
- return systemdArgs;
3739
- }
3740
- async function launchAction(name, args) {
3741
- const actionId = generateActionId(name);
3742
- const dir = actionLogDir();
3743
- mkdirSync3(dir, { recursive: true });
3744
- cleanupStaleLogs();
3745
- const logPath = actionLogPath(actionId);
3746
- const unit = `maxy-action-${actionId}`;
3747
- const systemdArgs = buildSystemdArgs(name, args, actionId, logPath);
3748
- await new Promise((resolveP, rejectP) => {
3749
- const proc = spawn("systemd-run", systemdArgs, { stdio: ["ignore", "pipe", "pipe"] });
3750
- let stderr = "";
3751
- proc.stderr.on("data", (chunk) => {
3752
- stderr += chunk.toString();
3753
- });
3754
- proc.on("close", (code) => {
3755
- if (code === 0) resolveP();
3756
- else rejectP(new Error(`systemd-run exited ${code}: ${stderr.trim().slice(0, 400)}`));
3757
- });
3758
- proc.on("error", rejectP);
3759
- });
3760
- console.log(`[action-runner] launched action=${name} id=${actionId} unit=${unit} log=${logPath}`);
3761
- return { actionId, unit, logPath };
3762
- }
3763
- function parseUnitShow(out) {
3764
- const kv = {};
3765
- for (const line of out.split("\n")) {
3766
- const eq = line.indexOf("=");
3767
- if (eq > 0) kv[line.slice(0, eq)] = line.slice(eq + 1);
3768
- }
3769
- const loadState = kv.LoadState ?? "";
3770
- if (loadState !== "loaded" || !kv.ActiveState) {
3771
- return { status: null, loadState };
3772
- }
3773
- return {
3774
- status: {
3775
- activeState: kv.ActiveState,
3776
- subState: kv.SubState ?? "",
3777
- mainPid: kv.MainPID ? Number(kv.MainPID) || null : null,
3778
- execMainStatus: kv.ExecMainStatus ? Number(kv.ExecMainStatus) : null,
3779
- execMainSignal: null
3780
- // systemd reports signal in SIGNAL (SIGTERM etc.), deferred — code is enough for our contract
3781
- },
3782
- loadState
3783
- };
3784
- }
3785
- async function showUnit(unit) {
3786
- return new Promise((resolveP) => {
3787
- const proc = spawn(
3788
- "systemctl",
3789
- ["--user", "show", unit, "--property=ActiveState,SubState,MainPID,ExecMainStatus,ExecMainCode,LoadState"],
3790
- { stdio: ["ignore", "pipe", "pipe"] }
3791
- );
3792
- let out = "";
3793
- proc.stdout.on("data", (chunk) => {
3794
- out += chunk.toString();
3795
- });
3796
- proc.on("close", () => resolveP(parseUnitShow(out)));
3797
- proc.on("error", () => resolveP({ status: null, loadState: "error" }));
3798
- });
3799
- }
3800
- async function getUnitStatus(unit) {
3801
- return (await showUnit(unit)).status;
3802
- }
3803
- var HEARTBEAT_INTERVAL_MS = 5e3;
3804
- var exitEmittedFor = /* @__PURE__ */ new Set();
3805
- var PHASE_BRACKETED = /\[(\d+)\/(\d+)\]/;
3806
- var PHASE_STREAM_LOG = /step=([a-z0-9-]+)/;
3807
- function extractPhase(text) {
3808
- const m1 = text.match(PHASE_BRACKETED);
3809
- if (m1) return m1[0];
3810
- const m2 = text.match(PHASE_STREAM_LOG);
3811
- if (m2) return m2[1];
3812
- return null;
3813
- }
3814
- function readLogTailLines(path, tailLines) {
3815
- if (!existsSync3(path)) return [];
3816
- let raw2;
3817
- try {
3818
- raw2 = readFileSync3(path, "utf-8");
3819
- } catch {
3820
- return [];
3821
- }
3822
- const all = raw2.split("\n");
3823
- if (all.length <= tailLines) return all;
3824
- return all.slice(all.length - tailLines);
3825
- }
3826
- async function* streamActionEvents(opts) {
3827
- const { actionId, unit, signal } = opts;
3828
- const logPath = actionLogPath(actionId);
3829
- const startedAt = Date.now();
3830
- let lastLineAt = startedAt;
3831
- let lastPhase = null;
3832
- let byteOffset = opts.fromByteOffset ?? 0;
3833
- const actionName = actionNameFromId(actionId);
3834
- const reconcileFromLog = isKnownAction(actionName) ? WHITELIST[actionName].reconcileFromLog : void 0;
3835
- if (existsSync3(logPath)) {
3836
- const snapshot = statSync(logPath).size;
3837
- if (byteOffset < snapshot) {
3838
- for await (const ev of readLogLines(logPath, byteOffset, snapshot)) {
3839
- const phase = extractPhase(ev.text);
3840
- if (phase) lastPhase = phase;
3841
- lastLineAt = ev.ts;
3842
- byteOffset = ev.byteOffset;
3843
- yield ev;
3844
- if (signal.aborted) return;
3845
- }
3846
- }
3847
- }
3848
- if (exitEmittedFor.has(actionId)) {
3849
- return;
3850
- }
3851
- let watching = true;
3852
- let hbTimer = null;
3853
- const queue = [];
3854
- let resolveNext = null;
3855
- function push(ev) {
3856
- queue.push(ev);
3857
- if (resolveNext) {
3858
- const r = resolveNext;
3859
- resolveNext = null;
3860
- r();
3861
- }
3862
- }
3863
- const watcher = existsSync3(logPath) ? watch(logPath, async () => {
3864
- if (!watching) return;
3865
- try {
3866
- const size = statSync(logPath).size;
3867
- if (size <= byteOffset) return;
3868
- for await (const ev of readLogLines(logPath, byteOffset, size)) {
3869
- const phase = extractPhase(ev.text);
3870
- if (phase) lastPhase = phase;
3871
- lastLineAt = ev.ts;
3872
- byteOffset = ev.byteOffset;
3873
- push(ev);
3874
- }
3875
- } catch (err) {
3876
- console.error(`[action-runner] watcher error action=${actionId}: ${err}`);
3877
- }
3878
- }) : null;
3879
- hbTimer = setInterval(async () => {
3880
- const probe = await showUnit(unit);
3881
- const status = probe.status;
3882
- const now = Date.now();
3883
- push({
3884
- type: "heartbeat",
3885
- ts: now,
3886
- elapsed_since_last_line_ms: now - lastLineAt,
3887
- systemd_state: status ? status.activeState : "unknown",
3888
- pid: status ? status.mainPid : null,
3889
- last_phase: lastPhase
3890
- });
3891
- if (!status) {
3892
- if (exitEmittedFor.has(actionId)) {
3893
- watching = false;
3894
- return;
3895
- }
3896
- exitEmittedFor.add(actionId);
3897
- console.log(`[action-runner] unit-not-loaded action=${actionNameFromId(actionId)} id=${actionId} loadState=${probe.loadState || "empty"}`);
3898
- const reconciled = reconcileFromLog ? reconcileFromLog(readLogTailLines(logPath, 200)) : null;
3899
- const reconcileMs = Date.now() - startedAt;
3900
- if (reconciled) {
3901
- console.log(
3902
- `[action-runner] reconcile actionId=${actionId} result=${reconciled.kind} source=persisted-log ms=${reconcileMs}`
3903
- );
3904
- push({
3905
- type: "exit",
3906
- ts: Date.now(),
3907
- code: reconciled.code,
3908
- signal: null,
3909
- duration_ms: reconcileMs,
3910
- source: "persisted-log",
3911
- ...reconciled.kind === "restart-in-progress" ? { kind: "restart-in-progress" } : {}
3912
- });
3913
- } else {
3914
- console.log(
3915
- `[action-runner] reconcile actionId=${actionId} result=unresolved source=persisted-log ms=${reconcileMs}`
3916
- );
3917
- push({
3918
- type: "exit",
3919
- ts: Date.now(),
3920
- code: null,
3921
- signal: null,
3922
- duration_ms: reconcileMs,
3923
- source: "persisted-log"
3924
- });
3925
- }
3926
- watching = false;
3927
- return;
3928
- }
3929
- if (status.activeState !== "active" && status.activeState !== "activating") {
3930
- if (exitEmittedFor.has(actionId)) {
3931
- watching = false;
3932
- return;
3933
- }
3934
- exitEmittedFor.add(actionId);
3935
- const finalStatus = await getUnitStatus(unit);
3936
- if (finalStatus?.execMainStatus == null && reconcileFromLog) {
3937
- const reconciled = reconcileFromLog(readLogTailLines(logPath, 200));
3938
- const reconcileMs = Date.now() - startedAt;
3939
- if (reconciled) {
3940
- console.log(
3941
- `[action-runner] reconcile actionId=${actionId} result=${reconciled.kind} source=persisted-log ms=${reconcileMs}`
3942
- );
3943
- push({
3944
- type: "exit",
3945
- ts: Date.now(),
3946
- code: reconciled.code,
3947
- signal: null,
3948
- duration_ms: reconcileMs,
3949
- source: "persisted-log",
3950
- ...reconciled.kind === "restart-in-progress" ? { kind: "restart-in-progress" } : {}
3951
- });
3952
- watching = false;
3953
- return;
3954
- }
3955
- }
3956
- const ms = Date.now() - startedAt;
3957
- const source = finalStatus?.execMainStatus != null ? "systemd" : "persisted-log";
3958
- const result = finalStatus?.execMainStatus === 0 ? "succeeded" : finalStatus?.execMainStatus != null ? "failed" : "unresolved";
3959
- console.log(
3960
- `[action-runner] reconcile actionId=${actionId} result=${result} source=${source} ms=${ms}`
3961
- );
3962
- push({
3963
- type: "exit",
3964
- ts: Date.now(),
3965
- code: finalStatus?.execMainStatus ?? null,
3966
- signal: null,
3967
- duration_ms: ms,
3968
- source
3969
- });
3970
- watching = false;
3971
- }
3972
- }, HEARTBEAT_INTERVAL_MS);
3973
- try {
3974
- while (watching && !signal.aborted) {
3975
- if (queue.length === 0) {
3976
- await new Promise((r) => {
3977
- resolveNext = r;
3978
- });
3979
- }
3980
- while (queue.length > 0) {
3981
- const ev = queue.shift();
3982
- yield ev;
3983
- if (ev.type === "exit") {
3984
- watching = false;
3985
- break;
3986
- }
3987
- }
3988
- }
3989
- } finally {
3990
- if (hbTimer) clearInterval(hbTimer);
3991
- watcher?.close();
3992
- }
3993
- }
3994
- async function* readLogLines(path, fromOffset, toOffset) {
3995
- const stream = createReadStream(path, { start: fromOffset, end: toOffset - 1, encoding: "utf-8" });
3996
- let buffer = "";
3997
- let offset = fromOffset;
3998
- for await (const chunk of stream) {
3999
- buffer += chunk;
4000
- let nl;
4001
- while ((nl = buffer.indexOf("\n")) !== -1) {
4002
- const text = buffer.slice(0, nl);
4003
- offset += Buffer.byteLength(text, "utf-8") + 1;
4004
- buffer = buffer.slice(nl + 1);
4005
- yield { type: "line", ts: Date.now(), stream: "stdout", text, byteOffset: offset };
4006
- }
4007
- }
4008
- }
4009
- var RATE_LIMIT_WINDOW_MS = 6e4;
4010
- var RATE_LIMIT_MAX_ATTEMPTS = 5;
4011
- var rateLimitBuckets = /* @__PURE__ */ new Map();
4012
- function rateLimitCheck(ip) {
4013
- const now = Date.now();
4014
- const bucket = rateLimitBuckets.get(ip);
4015
- if (!bucket || now - bucket.windowStart > RATE_LIMIT_WINDOW_MS) {
4016
- rateLimitBuckets.set(ip, { count: 1, windowStart: now });
4017
- return "ok";
4018
- }
4019
- bucket.count += 1;
4020
- if (bucket.count > RATE_LIMIT_MAX_ATTEMPTS) return "limited";
4021
- return "ok";
4022
- }
4023
- async function primeSudo(password, clientIp) {
4024
- if (!password) return "invalid";
4025
- if (rateLimitCheck(clientIp) === "limited") return "limited";
4026
- return new Promise((resolveP) => {
4027
- const proc = spawn("sudo", ["-S", "-v"], { stdio: ["pipe", "ignore", "pipe"] });
4028
- let stderr = "";
4029
- proc.stderr.on("data", (chunk) => {
4030
- stderr += chunk.toString();
4031
- });
4032
- proc.on("close", (code) => {
4033
- if (code === 0) return resolveP("ok");
4034
- if (/incorrect password|authentication failure/i.test(stderr)) return resolveP("invalid");
4035
- console.error(`[action-runner] sudo-prime failed code=${code} stderr=${stderr.slice(0, 200)}`);
4036
- resolveP("error");
4037
- });
4038
- proc.on("error", (err) => {
4039
- console.error(`[action-runner] sudo-prime spawn error: ${err}`);
4040
- resolveP("error");
4041
- });
4042
- proc.stdin.write(`${password}
4043
- `);
4044
- proc.stdin.end();
4045
- });
4046
- }
4047
- async function waitForExit(unit, timeoutMs = 10 * 60 * 1e3) {
4048
- const deadline = Date.now() + timeoutMs;
4049
- while (Date.now() < deadline) {
4050
- const status = await getUnitStatus(unit);
4051
- if (!status) return null;
4052
- if (status.activeState !== "active" && status.activeState !== "activating") return status;
4053
- await delay(500);
4054
- }
4055
- return null;
4056
- }
4057
-
4058
- export {
4059
- Hono2 as Hono,
4060
- getRequestListener,
4061
- serve,
4062
- MAXY_DIR,
4063
- BRAND_NAME,
4064
- COMMERCIAL_MODE,
4065
- VNC_DISPLAY,
4066
- RFB_PORT,
4067
- WEBSOCKIFY_PORT,
4068
- CDP_PORT,
4069
- CHROMIUM_PROFILE_DIR,
4070
- USERS_FILE,
4071
- LOG_DIR,
4072
- BIN_DIR,
4073
- TELEGRAM_WEBHOOK_SECRET_FILE,
4074
- TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE,
4075
- CLAUDE_CREDENTIALS_FILE,
4076
- vncLog,
4077
- newCorrId,
4078
- sanitizeClientCorrId,
4079
- httpLog,
4080
- websockifyLog,
4081
- browserViewerLog,
4082
- validatePasswordStrength,
4083
- isPasswordValid,
4084
- hashPassword,
4085
- verifyPassword,
4086
- isRemoteAuthConfigured,
4087
- setRemotePassword,
4088
- verifyRemotePassword,
4089
- createRemoteSession,
4090
- describeRemoteSession,
4091
- resolveClientIp,
4092
- checkRateLimit,
4093
- recordFailedAttempt,
4094
- clearRateLimit,
4095
- renderLoginPage,
4096
- canAccessAdmin,
4097
- parseCookieValue,
4098
- agentLogStream,
4099
- clearSessionHistory,
4100
- compactSession,
4101
- completeGrantSetup,
4102
- findMissingPlugins,
4103
- fingerprintSessionKey,
4104
- getAccountIdForSession,
4105
- getAgentNameForSession,
4106
- getConversationIdForSession,
4107
- getDefaultAccountId,
4108
- getGrantForSession,
4109
- getGroupSlugForSession,
4110
- getRoleForSession,
4111
- getSessionMessages,
4112
- getStreamLogHandle,
4113
- getUserIdForSession,
4114
- getUserNameForSession,
4115
- getVisitorIdForSession,
4116
- hasStubAccountDir,
4117
- invokeAgent,
4118
- listAdminSessionsInProgress,
4119
- mintAdminSessionToken,
4120
- registerGrantSession,
4121
- registerResumedSession,
4122
- registerSession,
4123
- resolveAccount,
4124
- resolveAgentConfig,
4125
- resolveDefaultAgentSlug,
4126
- resolveUserAccounts,
4127
- setAgentSessionId,
4128
- setConversationIdForSession,
4129
- setGroupContextForSession,
4130
- setWantsPriorConversation,
4131
- sigtermFlushStreamLogs,
4132
- streamLogPathFor,
4133
- unregisterSession,
4134
- validateAgentSlug,
4135
- validateSession,
4136
- safeJson,
4137
- requireAdminSession,
4138
- tryCookieBridgeForConversation,
4139
- requireAdminCookie,
4140
- clientIpMiddleware,
4141
- actionLogPath,
4142
- isKnownAction,
4143
- isActionActive,
4144
- launchAction,
4145
- streamActionEvents,
4146
- primeSudo,
4147
- waitForExit
4148
- };