@camstack/server 1.2.682 → 1.2.684

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.684] - 2026-10-29
11
+
12
+ ### Changed
13
+
14
+ - A form posted through an addon's data plane (a vendor sign-in page) reaches the addon unchanged instead of re-encoded as JSON
15
+
16
+ ## [1.2.683] - 2026-10-28
17
+
18
+ ### Changed
19
+
20
+ - Agents found on the network lists each agent's name
21
+
10
22
  ## [1.2.682] - 2026-10-28
11
23
 
12
24
  ### Changed
@@ -907,6 +907,7 @@ async function startAgent(configPath) {
907
907
  void agentMdns.sync({
908
908
  nodeId: config.nodeId,
909
909
  version: agentVersion,
910
+ name: config.name,
910
911
  port: config.statusPort ?? 4444,
911
912
  state: pairing.state(),
912
913
  leafFingerprint: (0, agent_pairing_store_js_1.pairedLeafFingerprint)(config.configPath),
@@ -954,6 +955,7 @@ async function startAgent(configPath) {
954
955
  const pairing = new system_1.AgentPairing({
955
956
  nodeId: config.nodeId,
956
957
  version: agentVersion,
958
+ name: config.name,
957
959
  isPaired: () => (0, agent_pairing_store_js_1.isAgentPaired)(config.configPath),
958
960
  install: async (installed) => {
959
961
  (0, agent_pairing_store_js_1.persistPairing)(config.configPath, installed);
@@ -55,6 +55,8 @@ const DiscoveredAgentSchema = zod_1.z.object({
55
55
  host: zod_1.z.string(),
56
56
  port: zod_1.z.number(),
57
57
  version: zod_1.z.string().nullable(),
58
+ /** The agent's display name; `null` when it does not tell (an older agent). */
59
+ name: zod_1.z.string().nullable(),
58
60
  state: zod_1.z.enum(['unpaired', 'pending', 'paired']),
59
61
  fingerprint: zod_1.z.string().nullable(),
60
62
  source: zod_1.z.enum(['mdns', 'address']),
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseFormBody = parseFormBody;
4
+ exports.registerFormBodyParser = registerFormBodyParser;
5
+ exports.dataPlaneReplayBody = dataPlaneReplayBody;
6
+ const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded';
7
+ const rawFormBodies = new WeakMap();
8
+ /** Parse a urlencoded body string into a plain object (duplicate keys collapse). */
9
+ function parseFormBody(raw) {
10
+ const result = {};
11
+ for (const [key, value] of new URLSearchParams(raw).entries()) {
12
+ result[key] = value;
13
+ }
14
+ return result;
15
+ }
16
+ /** Register the urlencoded parser: the parsed object for hub routes, the raw bytes on the side. */
17
+ function registerFormBodyParser(fastify) {
18
+ fastify.addContentTypeParser(FORM_CONTENT_TYPE, { parseAs: 'buffer' }, (req, body, done) => {
19
+ try {
20
+ const bytes = Buffer.isBuffer(body) ? body : Buffer.from(body);
21
+ rawFormBodies.set(req.raw, bytes);
22
+ done(null, parseFormBody(bytes.toString('utf8')));
23
+ }
24
+ catch (err) {
25
+ done(err instanceof Error ? err : new Error(String(err)), undefined);
26
+ }
27
+ });
28
+ }
29
+ /**
30
+ * The bytes a data-plane upstream receives. Never a re-serialised object: a
31
+ * parsed body with no recorded raw bytes is a bug path, logged (no values) and
32
+ * refused with `null`.
33
+ */
34
+ function dataPlaneReplayBody(raw, body, logger) {
35
+ if (body === undefined)
36
+ return undefined;
37
+ if (Buffer.isBuffer(body))
38
+ return body;
39
+ if (typeof body === 'string')
40
+ return Buffer.from(body);
41
+ const recorded = rawFormBodies.get(raw);
42
+ if (recorded !== undefined)
43
+ return recorded;
44
+ // A JSON body is re-serialised from its parsed object (Fastify's JSON parser
45
+ // keeps no bytes); every other parsed object has lost its original encoding.
46
+ const contentType = String(raw.headers['content-type'] ?? '');
47
+ if (/^application\/(?:[\w.+-]+\+)?json\b/i.test(contentType)) {
48
+ return Buffer.from(JSON.stringify(body));
49
+ }
50
+ logger.error('data-plane request body was parsed but its raw bytes were not recorded', {
51
+ tags: { contentType: String(raw.headers['content-type'] ?? ''), url: raw.url ?? '' },
52
+ });
53
+ return null;
54
+ }
@@ -9,6 +9,7 @@ exports.registerOauth2Routes = registerOauth2Routes;
9
9
  const types_1 = require("@camstack/types");
10
10
  const session_cookie_js_1 = require("../../auth/session-cookie.js");
11
11
  const app_page_strings_js_1 = require("../../auth/app-page-strings.js");
12
+ const form_body_js_1 = require("../data-plane/form-body.js");
12
13
  const consent_page_js_1 = require("./consent-page.js");
13
14
  const private_host_js_1 = require("./private-host.js");
14
15
  /**
@@ -328,27 +329,10 @@ async function readIntegrationRegistry(registry) {
328
329
  .filter((addonId) => !registry.hasProvider('oauth-integration', addonId));
329
330
  return { descriptors, pendingAddons };
330
331
  }
331
- /** Parse an application/x-www-form-urlencoded body string into a plain object. */
332
- function parseFormBody(raw) {
333
- const params = new URLSearchParams(raw);
334
- const result = {};
335
- for (const [key, value] of params.entries()) {
336
- result[key] = value;
337
- }
338
- return result;
339
- }
340
332
  function registerOauth2Routes(fastify, deps) {
341
- // Register a content-type parser for application/x-www-form-urlencoded so that
342
- // consent POST and token POST can read form bodies. @fastify/formbody is not in
343
- // the project's dependencies; we use URLSearchParams (Node built-in) instead.
344
- fastify.addContentTypeParser('application/x-www-form-urlencoded', { parseAs: 'string' }, (_req, body, done) => {
345
- try {
346
- done(null, parseFormBody(body));
347
- }
348
- catch (err) {
349
- done(err, undefined);
350
- }
351
- });
333
+ // Form bodies (consent POST, token POST): parsed object for these routes, raw
334
+ // bytes recorded for the data plane (api/data-plane/form-body.ts).
335
+ (0, form_body_js_1.registerFormBodyParser)(fastify);
352
336
  // ─── GET /api/oauth2/authorize ────────────────────────────────────────────
353
337
  // Mounted under /api/* so it is naturally excluded from the SPA catch-all,
354
338
  // the PWA service-worker navigate fallback, and the Vite dev proxy — no
@@ -44,7 +44,7 @@ class HubAgentPairing {
44
44
  }
45
45
  if (byKey.has(key))
46
46
  continue;
47
- byKey.set(key, this.describe(m.nodeId, m.endpoint.host, m.endpoint.port, { version: m.version, state: m.state, fingerprint: null }, 'address'));
47
+ byKey.set(key, this.describe(m.nodeId, m.endpoint.host, m.endpoint.port, { version: m.version, name: m.name, state: m.state, fingerprint: null }, 'address'));
48
48
  }
49
49
  return { agents: [...byKey.values()], scannedAt: this.scanned?.at ?? null };
50
50
  }
@@ -56,10 +56,11 @@ class HubAgentPairing {
56
56
  endpoint,
57
57
  nodeId: hello.nodeId,
58
58
  version: hello.version,
59
+ name: hello.name ?? null,
59
60
  state: hello.state,
60
61
  seenAt: this.now(),
61
62
  });
62
- return this.describe(hello.nodeId, host, port, { version: hello.version, state: hello.state, fingerprint: null }, 'address');
63
+ return this.describe(hello.nodeId, host, port, { version: hello.version, name: hello.name ?? null, state: hello.state, fingerprint: null }, 'address');
63
64
  }
64
65
  async begin(endpoint) {
65
66
  const view = await this.deps.pairing.begin(endpoint);
@@ -112,6 +113,7 @@ class HubAgentPairing {
112
113
  host,
113
114
  port,
114
115
  version: facts.version,
116
+ name: facts.name,
115
117
  state: facts.state,
116
118
  fingerprint: facts.fingerprint,
117
119
  source,
package/dist/main.js CHANGED
@@ -71,6 +71,7 @@ const release_identity_1 = require("./core/release-identity");
71
71
  const runner_boot_check_1 = require("./core/runner-health/runner-boot-check");
72
72
  const hub_cluster_url_1 = require("./api/hub-cluster-url");
73
73
  const model_distributor_js_1 = require("./api/model-distributor.js");
74
+ const form_body_js_1 = require("./api/data-plane/form-body.js");
74
75
  const oauth2_routes_js_1 = require("./api/oauth2/oauth2-routes.js");
75
76
  const embed_auth_route_js_1 = require("./auth/embed-auth-route.js");
76
77
  const live_user_js_1 = require("./auth/live-user.js");
@@ -1128,13 +1129,12 @@ async function bootstrap() {
1128
1129
  }
1129
1130
  if (shareHeaders !== null)
1130
1131
  Object.assign(request.raw.headers, shareHeaders);
1132
+ const replayBody = (0, form_body_js_1.dataPlaneReplayBody)(request.raw, request.body, addonGateLogger);
1133
+ if (replayBody === null) {
1134
+ return reply.status(500).send({ error: 'Request body could not be forwarded' });
1135
+ }
1131
1136
  // Take over the socket — `proxyToUpstream` drives the raw response.
1132
1137
  reply.hijack();
1133
- const replayBody = request.body === undefined
1134
- ? undefined
1135
- : Buffer.isBuffer(request.body)
1136
- ? request.body
1137
- : Buffer.from(typeof request.body === 'string' ? request.body : JSON.stringify(request.body));
1138
1138
  (0, system_1.proxyToUpstream)({
1139
1139
  baseUrl: dpMatch.endpoint.baseUrl,
1140
1140
  secret: dpMatch.endpoint.secret,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.682",
3
+ "version": "1.2.684",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,20 +49,20 @@
49
49
  ]
50
50
  },
51
51
  "dependencies": {
52
- "@camstack/addon-admin-ui": "1.2.510",
52
+ "@camstack/addon-admin-ui": "1.2.512",
53
53
  "@camstack/addon-agent-ui": "1.2.289",
54
54
  "@camstack/addon-auth": "1.2.170",
55
55
  "@camstack/addon-benchmark": "1.2.288",
56
56
  "@camstack/addon-decoder-nodeav": "1.2.144",
57
57
  "@camstack/addon-notifiers": "1.2.623",
58
58
  "@camstack/addon-pipeline": "1.2.633",
59
- "@camstack/addon-pipeline-orchestrator": "1.2.637",
59
+ "@camstack/addon-pipeline-orchestrator": "1.2.638",
60
60
  "@camstack/addon-post-analysis": "1.2.650",
61
- "@camstack/develop": "0.1.38",
61
+ "@camstack/develop": "0.1.39",
62
62
  "@camstack/sdk": "1.2.154",
63
- "@camstack/system": "1.2.513",
64
- "@camstack/types": "1.2.439",
65
- "@camstack/ui-library": "1.2.346",
63
+ "@camstack/system": "1.2.514",
64
+ "@camstack/types": "1.2.440",
65
+ "@camstack/ui-library": "1.2.347",
66
66
  "@fastify/compress": "^9.0.0",
67
67
  "@fastify/cookie": "^11.0.2",
68
68
  "@fastify/cors": "^11.2.0",