@rubytech/create-maxy 1.0.621 → 1.0.622
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/lib/device-url/dist/index.d.ts +44 -0
- package/payload/platform/lib/device-url/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/device-url/dist/index.js +68 -0
- package/payload/platform/lib/device-url/dist/index.js.map +1 -0
- package/payload/platform/lib/device-url/src/index.ts +78 -0
- package/payload/platform/lib/device-url/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +12 -5
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +45 -29
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +30 -17
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +78 -34
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
- package/payload/platform/plugins/cloudflare/references/setup-guide.md +5 -6
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +3 -4
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
- package/payload/server/public/assets/admin-BxVuKRJZ.js +352 -0
- package/payload/server/public/assets/{public-ZM0fHAOE.js → public-Bgm9WQFZ.js} +2 -2
- package/payload/server/public/assets/useVoiceRecorder-BORuG_su.css +1 -0
- package/payload/server/public/assets/useVoiceRecorder-CiYPZu3g.js +44 -0
- package/payload/server/public/index.html +3 -3
- package/payload/server/public/public.html +3 -3
- package/payload/server/server.js +259 -15
- package/payload/server/public/assets/admin-D7LRdkYB.js +0 -352
- package/payload/server/public/assets/useVoiceRecorder-CaFVzk8y.css +0 -1
- package/payload/server/public/assets/useVoiceRecorder-OB_Gtr0e.js +0 -43
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Maxy</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/admin-
|
|
8
|
+
<script type="module" crossorigin src="/assets/admin-BxVuKRJZ.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-Be6NvmcD.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-rov5CBGT.js">
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-CiYPZu3g.js">
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-BORuG_su.css">
|
|
13
13
|
<link rel="stylesheet" href="/brand-defaults.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Maxy</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/public-
|
|
8
|
+
<script type="module" crossorigin src="/assets/public-Bgm9WQFZ.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-Be6NvmcD.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-rov5CBGT.js">
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-CiYPZu3g.js">
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-BORuG_su.css">
|
|
13
13
|
<link rel="stylesheet" href="/brand-defaults.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
package/payload/server/server.js
CHANGED
|
@@ -10574,24 +10574,64 @@ function defaultRules() {
|
|
|
10574
10574
|
suggestedAction: "This laptop is signed into a Cloudflare account that does not own the hostnames the tunnel is configured to serve. Run `tunnel-status` to confirm, then tell the operator verbatim: 'The tunnel is running on this laptop but nothing from the internet is reaching it. The Cloudflare account this laptop is signed into doesn't own your domain. Open Cloudflare in your browser \u2014 is the account name in the top-left the one that owns your domain? If not, switch to the correct one, then tell me and I will re-sign-in.' When the operator confirms the correct account is selected, run `tunnel-login force=true`."
|
|
10575
10575
|
},
|
|
10576
10576
|
{
|
|
10577
|
-
// Task
|
|
10578
|
-
//
|
|
10579
|
-
//
|
|
10580
|
-
//
|
|
10581
|
-
//
|
|
10582
|
-
//
|
|
10583
|
-
//
|
|
10584
|
-
// that
|
|
10585
|
-
//
|
|
10586
|
-
//
|
|
10577
|
+
// Task 545: tunnel-login's terminal-failure class — cloudflared's
|
|
10578
|
+
// login process died without writing cert.pem. Covers every reason
|
|
10579
|
+
// the handler emits on the `failed` branch: either an unknown exit
|
|
10580
|
+
// (`-without-cert`), an exit preceded by the courtesy browser-launch
|
|
10581
|
+
// marker (`-with-marker`), auth URL never produced (`-timeout`), or
|
|
10582
|
+
// crashed before producing it at all. Task 541's original pattern
|
|
10583
|
+
// matched `reason=browser-launch-fetch-error` — Task 545 retired
|
|
10584
|
+
// that reason because the marker alone is no longer terminal
|
|
10585
|
+
// (cloudflared keeps its OAuth-callback loop alive after emitting
|
|
10586
|
+
// it). Use this pattern for any new terminal reason the handler
|
|
10587
|
+
// gains: extend the alternation rather than adding parallel rules.
|
|
10587
10588
|
id: "cloudflare-tunnel-login-failed",
|
|
10588
|
-
name: "Cloudflare tunnel-login
|
|
10589
|
+
name: "Cloudflare tunnel-login process terminated without writing cert",
|
|
10589
10590
|
type: "silent-catch",
|
|
10590
10591
|
logSource: "any",
|
|
10591
|
-
pattern: "\\[cloudflare:tunnel-login:failed\\] reason=
|
|
10592
|
+
pattern: "\\[cloudflare:tunnel-login:failed\\] reason=(login-process-exited-without-cert|login-process-exited-with-marker|auth-url-timeout|process-exited-before-auth-url)",
|
|
10592
10593
|
thresholdCount: 0,
|
|
10593
10594
|
thresholdWindowMinutes: 0,
|
|
10594
|
-
suggestedAction: "cloudflared
|
|
10595
|
+
suggestedAction: "The cloudflared login process died before cert.pem landed on disk. For `login-process-exited-*` reasons the tunnel-login tool detected the dead process on the next call and has already respawned it \u2014 relay the new sign-in URL and wait for the operator to authorize in the VNC browser. For `auth-url-timeout` / `process-exited-before-auth-url`, the tool's most recent call returned an error and did not respawn \u2014 call `tunnel-login` again to spawn a fresh attempt. Never open the Cloudflare dashboard in any other surface; the only auth path is the sign-in URL the tool produces."
|
|
10596
|
+
},
|
|
10597
|
+
{
|
|
10598
|
+
// Task 545: non-terminal advisory. cloudflared's browser-launch
|
|
10599
|
+
// subcommand failed (DISPLAY unreachable, xdg-open absent, etc.) but
|
|
10600
|
+
// its OAuth-callback listener is still running — the login can still
|
|
10601
|
+
// complete if a human opens the URL. This rule fires so the admin
|
|
10602
|
+
// agent can relay "open the URL yourself" to the operator the moment
|
|
10603
|
+
// the condition appears, rather than waiting for the operator to
|
|
10604
|
+
// notice nothing is happening in their browser. Task 546 will
|
|
10605
|
+
// obsolete the advisory by rendering auth URLs that auto-open in
|
|
10606
|
+
// the VNC browser.
|
|
10607
|
+
id: "cloudflare-tunnel-login-browser-launch-failed",
|
|
10608
|
+
name: "cloudflared couldn't open the sign-in URL (login still live)",
|
|
10609
|
+
type: "silent-catch",
|
|
10610
|
+
logSource: "any",
|
|
10611
|
+
pattern: "\\[cloudflare:tunnel-login:browser-launch-failed\\]",
|
|
10612
|
+
thresholdCount: 0,
|
|
10613
|
+
thresholdWindowMinutes: 0,
|
|
10614
|
+
suggestedAction: "cloudflared's browser-launch courtesy failed on this laptop, but the sign-in URL is still live \u2014 the process is waiting for the OAuth callback. Tell the operator to open the sign-in URL in the VNC browser themselves and complete the authorization. The tunnel-login tool already includes the URL and the advisory in its most recent response; do not restart the login (that would invalidate the URL the operator is about to open)."
|
|
10615
|
+
},
|
|
10616
|
+
{
|
|
10617
|
+
// Task 545: raw-log surface. cloudflared-login.log is now read by
|
|
10618
|
+
// the review-detector's `cloudflared` source (see sources.ts). The
|
|
10619
|
+
// literal "Failed to fetch resource" is emitted by cloudflared
|
|
10620
|
+
// itself when its browser-launch subcommand can't reach a display.
|
|
10621
|
+
// This rule catches the cloudflared-side event even if the MCP
|
|
10622
|
+
// handler's classification drifts or the platform is restarting
|
|
10623
|
+
// mid-login and the advisory log line is not yet written. Keeping
|
|
10624
|
+
// this rule on a different source (log-line, not MCP stderr) makes
|
|
10625
|
+
// the detection redundant in the "defence in depth" sense — if the
|
|
10626
|
+
// MCP classification ever regresses, this still fires.
|
|
10627
|
+
id: "cloudflared-login-browser-launch-failed-raw",
|
|
10628
|
+
name: "cloudflared login \u2014 browser-launch fetch error in log",
|
|
10629
|
+
type: "silent-catch",
|
|
10630
|
+
logSource: "cloudflared",
|
|
10631
|
+
pattern: "Failed to fetch resource",
|
|
10632
|
+
thresholdCount: 0,
|
|
10633
|
+
thresholdWindowMinutes: 0,
|
|
10634
|
+
suggestedAction: "cloudflared-login.log shows the browser-launch courtesy failed. The sign-in URL from the last tunnel-login call is still live \u2014 the cloudflared process waits for the OAuth callback regardless of whether it could open the browser itself. Tell the operator to open the sign-in URL manually in the VNC browser on the device."
|
|
10595
10635
|
},
|
|
10596
10636
|
{
|
|
10597
10637
|
// Task 540: cloudflared.log is the one file most likely to carry the
|
|
@@ -10629,6 +10669,33 @@ function defaultRules() {
|
|
|
10629
10669
|
thresholdWindowMinutes: 60,
|
|
10630
10670
|
scope: "session",
|
|
10631
10671
|
suggestedAction: 'Agent emitted a choice-fork question ("Want me to X, or Y?") instead of a one-sided question or the prescribed action. Review Task 543 IDENTITY \xA7 Questions \u2014 the agent asked an opposing-axis question, or degraded a deterministic tool signal into a menu. The log sample shows the offending turn verbatim.'
|
|
10672
|
+
},
|
|
10673
|
+
{
|
|
10674
|
+
// Task 546: fires when the operator clicks a device-bound URL affordance
|
|
10675
|
+
// and the chat UI cannot drive the device browser — either CDP is
|
|
10676
|
+
// unreachable, the navigation timed out, or CDP returned an error. The
|
|
10677
|
+
// log line carries intent, hostname, and navigateResult so the admin
|
|
10678
|
+
// agent can name the affected flow and hostname verbatim on its next
|
|
10679
|
+
// turn. Every occurrence is worth surfacing (thresholdCount: 0) because
|
|
10680
|
+
// this is the exact class of silent failure Task 546 exists to close:
|
|
10681
|
+
// the operator clicked, nothing happened on the device, and if we don't
|
|
10682
|
+
// review the click telemetry the agent has no way to know the flow is
|
|
10683
|
+
// stuck.
|
|
10684
|
+
id: "device-url-click-failed",
|
|
10685
|
+
name: "Device-bound URL click failed to drive the VNC browser",
|
|
10686
|
+
type: "silent-catch",
|
|
10687
|
+
logSource: "server",
|
|
10688
|
+
// Enumerate the NavigateResult union explicitly rather than relying
|
|
10689
|
+
// on a (?!ok) negative lookahead anchored to a specific token order.
|
|
10690
|
+
// If a new member is added to the union in cdp-client.ts, this rule
|
|
10691
|
+
// must be updated in the same commit — the pattern is order-agnostic
|
|
10692
|
+
// (browser= and navigateResult= can appear in either order) and the
|
|
10693
|
+
// enumerated list compile-fails the source if it ever drifts from
|
|
10694
|
+
// the shared type in device-url-schema.ts.
|
|
10695
|
+
pattern: "\\[device-url:click\\][^\\n]*(?:browser=fallback|navigateResult=(?:timeout|cdp-unreachable|error))",
|
|
10696
|
+
thresholdCount: 0,
|
|
10697
|
+
thresholdWindowMinutes: 0,
|
|
10698
|
+
suggestedAction: "A device-bound URL click failed to drive Chromium on the device's VNC display. Identify the `intent` and `hostname` from the log line, then check the VNC surface: read `vnc-boot.log` and confirm Chromium on :99 is responding on CDP port 9222. If CDP is unreachable, the operator needs to restart the VNC stack; if CDP is reachable but navigation errored, the URL itself may be malformed upstream \u2014 grep the stream log for the originating `[device-url:render]` line."
|
|
10632
10699
|
}
|
|
10633
10700
|
];
|
|
10634
10701
|
}
|
|
@@ -10858,8 +10925,12 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
|
|
|
10858
10925
|
return existsSync8(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
|
|
10859
10926
|
}
|
|
10860
10927
|
if (logicalSource === "cloudflared") {
|
|
10861
|
-
const
|
|
10862
|
-
|
|
10928
|
+
const files2 = [];
|
|
10929
|
+
const daemon = resolve8(configDir2, "logs", "cloudflared.log");
|
|
10930
|
+
if (existsSync8(daemon)) files2.push({ logicalSource: "cloudflared", filepath: daemon });
|
|
10931
|
+
const login = resolve8(configDir2, "logs", "cloudflared-login.log");
|
|
10932
|
+
if (existsSync8(login)) files2.push({ logicalSource: "cloudflared", filepath: login });
|
|
10933
|
+
return files2;
|
|
10863
10934
|
}
|
|
10864
10935
|
const prefix = {
|
|
10865
10936
|
system: "claude-agent-stream-",
|
|
@@ -32133,6 +32204,174 @@ async function POST27(req, remoteAddress) {
|
|
|
32133
32204
|
}
|
|
32134
32205
|
}
|
|
32135
32206
|
|
|
32207
|
+
// app/lib/cdp-client.ts
|
|
32208
|
+
var CDP_HOST = "127.0.0.1";
|
|
32209
|
+
var CDP_PORT = 9222;
|
|
32210
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
32211
|
+
async function cdpNavigateNewTab(url2, opts = {}) {
|
|
32212
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32213
|
+
const endpoint = `http://${CDP_HOST}:${CDP_PORT}/json/new?${encodeURIComponent(url2)}`;
|
|
32214
|
+
let res;
|
|
32215
|
+
try {
|
|
32216
|
+
res = await fetch(endpoint, {
|
|
32217
|
+
method: "PUT",
|
|
32218
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
32219
|
+
});
|
|
32220
|
+
} catch (err) {
|
|
32221
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32222
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("ENOTFOUND")) {
|
|
32223
|
+
return { result: "cdp-unreachable", detail: msg };
|
|
32224
|
+
}
|
|
32225
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
32226
|
+
return { result: "timeout", detail: msg };
|
|
32227
|
+
}
|
|
32228
|
+
return { result: "error", detail: msg };
|
|
32229
|
+
}
|
|
32230
|
+
if (!res.ok) {
|
|
32231
|
+
const body = await res.text().catch(() => "<unreadable>");
|
|
32232
|
+
return {
|
|
32233
|
+
result: "error",
|
|
32234
|
+
detail: `CDP /json/new returned ${res.status}: ${body.slice(0, 200)}`
|
|
32235
|
+
};
|
|
32236
|
+
}
|
|
32237
|
+
let target;
|
|
32238
|
+
try {
|
|
32239
|
+
target = await res.json();
|
|
32240
|
+
} catch (err) {
|
|
32241
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32242
|
+
return { result: "error", detail: `CDP /json/new response not JSON: ${msg}` };
|
|
32243
|
+
}
|
|
32244
|
+
return { result: "ok", targetId: target?.id };
|
|
32245
|
+
}
|
|
32246
|
+
|
|
32247
|
+
// app/api/admin/device-browser/navigate/route.ts
|
|
32248
|
+
async function POST28(req, remoteAddress) {
|
|
32249
|
+
const TAG18 = "[device-url:click]";
|
|
32250
|
+
let body;
|
|
32251
|
+
try {
|
|
32252
|
+
body = await req.json();
|
|
32253
|
+
} catch (err) {
|
|
32254
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
32255
|
+
console.error(`${TAG18} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
|
|
32256
|
+
return Response.json(
|
|
32257
|
+
{ ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
|
|
32258
|
+
{ status: 400 }
|
|
32259
|
+
);
|
|
32260
|
+
}
|
|
32261
|
+
const url2 = typeof body.url === "string" ? body.url : "";
|
|
32262
|
+
const intent = typeof body.intent === "string" ? body.intent : "";
|
|
32263
|
+
const hostname4 = typeof body.hostname === "string" ? body.hostname : "";
|
|
32264
|
+
if (!url2) {
|
|
32265
|
+
console.error(`${TAG18} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
|
|
32266
|
+
return Response.json(
|
|
32267
|
+
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
|
|
32268
|
+
{ status: 400 }
|
|
32269
|
+
);
|
|
32270
|
+
}
|
|
32271
|
+
let parsed;
|
|
32272
|
+
try {
|
|
32273
|
+
parsed = new URL(url2);
|
|
32274
|
+
} catch {
|
|
32275
|
+
console.error(`${TAG18} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url2} browser=fallback navigateResult=error`);
|
|
32276
|
+
return Response.json(
|
|
32277
|
+
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
|
|
32278
|
+
{ status: 400 }
|
|
32279
|
+
);
|
|
32280
|
+
}
|
|
32281
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
32282
|
+
console.error(
|
|
32283
|
+
`${TAG18} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
|
|
32284
|
+
);
|
|
32285
|
+
return Response.json(
|
|
32286
|
+
{
|
|
32287
|
+
ok: false,
|
|
32288
|
+
navigateResult: "error",
|
|
32289
|
+
browser: "fallback",
|
|
32290
|
+
detail: `URL scheme ${parsed.protocol} is not permitted; only http and https are accepted`
|
|
32291
|
+
},
|
|
32292
|
+
{ status: 400 }
|
|
32293
|
+
);
|
|
32294
|
+
}
|
|
32295
|
+
const transport = resolveBrowserTransport(req, remoteAddress);
|
|
32296
|
+
const cdpOk = await ensureCdp(transport);
|
|
32297
|
+
if (!cdpOk) {
|
|
32298
|
+
console.error(
|
|
32299
|
+
`${TAG18} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname4)}`
|
|
32300
|
+
);
|
|
32301
|
+
return Response.json(
|
|
32302
|
+
{
|
|
32303
|
+
ok: false,
|
|
32304
|
+
navigateResult: "cdp-unreachable",
|
|
32305
|
+
browser: "fallback",
|
|
32306
|
+
detail: "Device browser (Chromium/CDP on :9222) did not start"
|
|
32307
|
+
},
|
|
32308
|
+
{ status: 502 }
|
|
32309
|
+
);
|
|
32310
|
+
}
|
|
32311
|
+
const outcome = await cdpNavigateNewTab(parsed.toString());
|
|
32312
|
+
const browser = outcome.result === "ok" ? "vnc" : "fallback";
|
|
32313
|
+
const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
|
|
32314
|
+
console.error(
|
|
32315
|
+
`${TAG18} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname4)} targetId=${outcome.targetId ?? "none"}${detailStr}`
|
|
32316
|
+
);
|
|
32317
|
+
if (outcome.result !== "ok") {
|
|
32318
|
+
return Response.json(
|
|
32319
|
+
{
|
|
32320
|
+
ok: false,
|
|
32321
|
+
navigateResult: outcome.result,
|
|
32322
|
+
browser: "fallback",
|
|
32323
|
+
detail: outcome.detail
|
|
32324
|
+
},
|
|
32325
|
+
{ status: 502 }
|
|
32326
|
+
);
|
|
32327
|
+
}
|
|
32328
|
+
return Response.json({
|
|
32329
|
+
ok: true,
|
|
32330
|
+
navigateResult: "ok",
|
|
32331
|
+
browser: "vnc",
|
|
32332
|
+
targetId: outcome.targetId
|
|
32333
|
+
});
|
|
32334
|
+
}
|
|
32335
|
+
|
|
32336
|
+
// app/api/admin/events/route.ts
|
|
32337
|
+
var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
32338
|
+
"device-url:render",
|
|
32339
|
+
"device-url:fallback-shown",
|
|
32340
|
+
"device-url:vnc-surface-shown",
|
|
32341
|
+
"device-url:malformed"
|
|
32342
|
+
]);
|
|
32343
|
+
async function POST29(req) {
|
|
32344
|
+
const TAG18 = "[admin:events]";
|
|
32345
|
+
let body;
|
|
32346
|
+
try {
|
|
32347
|
+
body = await req.json();
|
|
32348
|
+
} catch (err) {
|
|
32349
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
32350
|
+
console.error(`${TAG18} reject reason=body-not-json detail=${detail}`);
|
|
32351
|
+
return Response.json({ ok: false, detail: "Request body was not valid JSON" }, { status: 400 });
|
|
32352
|
+
}
|
|
32353
|
+
const event = typeof body.event === "string" ? body.event : "";
|
|
32354
|
+
if (!ALLOWED_EVENTS.has(event)) {
|
|
32355
|
+
console.error(`${TAG18} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
32356
|
+
return Response.json({ ok: false, detail: `Event "${event}" is not allowed` }, { status: 400 });
|
|
32357
|
+
}
|
|
32358
|
+
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
32359
|
+
const safeFields = {};
|
|
32360
|
+
for (const [key, value] of Object.entries(rawFields)) {
|
|
32361
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,39}$/.test(key)) continue;
|
|
32362
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
32363
|
+
safeFields[key] = value;
|
|
32364
|
+
}
|
|
32365
|
+
}
|
|
32366
|
+
const formatted = Object.entries(safeFields).map(([k, v]) => {
|
|
32367
|
+
if (typeof v !== "string") return `${k}=${String(v)}`;
|
|
32368
|
+
const trunc = v.length > 230 ? v.slice(0, 227) + "..." : v;
|
|
32369
|
+
return `${k}=${JSON.stringify(trunc)}`;
|
|
32370
|
+
}).join(" ");
|
|
32371
|
+
console.error(`[${event}] ${formatted}`);
|
|
32372
|
+
return Response.json({ ok: true });
|
|
32373
|
+
}
|
|
32374
|
+
|
|
32136
32375
|
// server/index.ts
|
|
32137
32376
|
var PLATFORM_ROOT12 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
32138
32377
|
var BRAND_JSON_PATH = PLATFORM_ROOT12 ? join13(PLATFORM_ROOT12, "config", "brand.json") : "";
|
|
@@ -32523,6 +32762,11 @@ app.delete(
|
|
|
32523
32762
|
(c) => DELETE2(c.req.raw, { params: Promise.resolve({ slug: c.req.param("slug") }) })
|
|
32524
32763
|
);
|
|
32525
32764
|
app.post("/api/admin/browser/launch", (c) => POST27(c.req.raw, c.env?.incoming?.socket?.remoteAddress));
|
|
32765
|
+
app.post(
|
|
32766
|
+
"/api/admin/device-browser/navigate",
|
|
32767
|
+
(c) => POST28(c.req.raw, c.env?.incoming?.socket?.remoteAddress)
|
|
32768
|
+
);
|
|
32769
|
+
app.post("/api/admin/events", (c) => POST29(c.req.raw));
|
|
32526
32770
|
app.get("/api/admin/sessions", (c) => GET15(c.req.raw));
|
|
32527
32771
|
app.post("/api/admin/sessions/new", (c) => POST24(c.req.raw));
|
|
32528
32772
|
app.delete(
|