@cjhyy/code-shell-server 0.9.25 → 0.9.27

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,4 +1,4 @@
1
- export type { LinkAuthorization, LinkConnectionInput, LinkErrorCode, LinkSnapshot, MaskedLinkConnection, TokenConnectionInput, } from "@cjhyy/code-shell-link";
1
+ export type { LinkAuthorization, LinkAuthMode, LinkAuthorizationInput, LinkAuthorizationResponse, LinkAuthorizationStep, LinkConnectionInput, LinkErrorCode, LinkSnapshot, MaskedLinkConnection, TokenConnectionInput, } from "@cjhyy/code-shell-link";
2
2
  export interface LinkOperationContext {
3
3
  ownerId: string;
4
4
  /** Rechecked after every external await and immediately before persistence. */
@@ -2,6 +2,7 @@ import { request as httpRequest } from "node:http";
2
2
  import { Transform } from "node:stream";
3
3
  import { WebSocket, WebSocketServer } from "ws";
4
4
  import { HUB_SESSION_COOKIE } from "../hub/auth-http.js";
5
+ import { createLinkBrowserHandoff } from "../links/browser-handoff.js";
5
6
  const UUID = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}";
6
7
  const PROJECT_PATH = new RegExp(`^/p/(${UUID})(/.*)$`);
7
8
  const API_PATH = /^\/api\/v1\/(?:configuration|mcp|skills|links|panels|panel-assets|files|sessions|uploads)(?:\/|$)/;
@@ -53,6 +54,7 @@ class ProxyError extends Error {
53
54
  }
54
55
  /** Private runtime credentials and cookies never cross this control-plane boundary. */
55
56
  export function createProjectRuntimeProxy(options) {
57
+ const linkBrowser = createLinkBrowserHandoff();
56
58
  const leases = new Map();
57
59
  const assets = new Map();
58
60
  const revokedOwners = new Set();
@@ -249,10 +251,10 @@ export function createProjectRuntimeProxy(options) {
249
251
  }
250
252
  return result;
251
253
  }
252
- async function innerJson(target, path, body, cookie, timeout = 10_000) {
254
+ async function innerJson(target, path, body, cookie, timeout = 10_000, method = "POST") {
253
255
  return await new Promise((resolve, reject) => {
254
256
  const request = httpRequest(new URL(path, target.url), {
255
- method: "POST",
257
+ method,
256
258
  headers: {
257
259
  origin: options.publicOrigin(),
258
260
  "content-type": "application/json",
@@ -338,12 +340,24 @@ export function createProjectRuntimeProxy(options) {
338
340
  const renew = req.method === "POST" &&
339
341
  incoming.statusCode === 200 &&
340
342
  /^\/api\/v1\/panels\/runtime\/([A-Za-z0-9_-]+)\/renew$/.exec(route.pathname);
341
- const bytes = prepare || renew ? await readBounded(incoming, MAX_PREPARE_BYTES) : undefined;
343
+ const linkCatalog = req.method === "GET" &&
344
+ route.pathname === "/api/v1/links" &&
345
+ incoming.statusCode === 200;
346
+ let bytes = prepare || renew || linkCatalog
347
+ ? await readBounded(incoming, MAX_PREPARE_BYTES)
348
+ : undefined;
342
349
  if (bytes) {
343
350
  await validate(lease);
344
351
  if (settled)
345
352
  return;
346
- if (prepare)
353
+ if (linkCatalog) {
354
+ const snapshot = JSON.parse(bytes.toString("utf8"));
355
+ if (snapshot.capabilities?.authorizationSteps === 1 &&
356
+ snapshot.capabilities?.remoteAuth === true)
357
+ snapshot.capabilities.browserHandoff = 1;
358
+ bytes = Buffer.from(JSON.stringify(snapshot));
359
+ }
360
+ else if (prepare)
347
361
  rememberPrepare(lease, bytes);
348
362
  else if (renew) {
349
363
  const { expiresAt } = JSON.parse(bytes.toString("utf8"));
@@ -361,6 +375,8 @@ export function createProjectRuntimeProxy(options) {
361
375
  (incoming.statusCode ?? 500) < 300)
362
376
  forgetAssets(lease, route.pathname.split("/").at(-1));
363
377
  for (const name of RESPONSE_HEADERS) {
378
+ if (linkCatalog && name === "content-length")
379
+ continue;
364
380
  const value = incoming.headers[name];
365
381
  if (value !== undefined)
366
382
  res.setHeader(name, value);
@@ -383,6 +399,8 @@ export function createProjectRuntimeProxy(options) {
383
399
  });
384
400
  }
385
401
  async function handle(req, res) {
402
+ if (await linkBrowser.handle(req, res))
403
+ return true;
386
404
  if (!(req.url ?? "").startsWith("/p/"))
387
405
  return false;
388
406
  try {
@@ -394,6 +412,43 @@ export function createProjectRuntimeProxy(options) {
394
412
  : await acquire(req, route.projectId);
395
413
  if (req.aborted || res.destroyed)
396
414
  return true;
415
+ const handoff = /^\/api\/v1\/links\/authorizations\/([a-f0-9-]{36})\/browser$/.exec(route.pathname);
416
+ if (handoff && req.method === "POST") {
417
+ const input = JSON.parse((await readBounded(req, 1024)).toString("utf8"));
418
+ if (!input ||
419
+ Array.isArray(input) ||
420
+ typeof input !== "object" ||
421
+ Object.keys(input).length)
422
+ throw new ProxyError(400, "Invalid browser authorization");
423
+ const target = `/api/v1/links/authorizations/${handoff[1]}`;
424
+ const readJob = async () => {
425
+ await validate(lease);
426
+ const result = await innerJson(lease.target, target, "", lease.cookie, 10_000, "GET");
427
+ await validate(lease);
428
+ const job = JSON.parse(result.bytes.toString("utf8"));
429
+ if (job.id !== handoff[1] || job.state !== "pending")
430
+ throw new ProxyError(409, "Authorization is no longer pending");
431
+ return job;
432
+ };
433
+ const launch = linkBrowser.create(await readJob(), {
434
+ check: async () => {
435
+ await readJob();
436
+ },
437
+ complete: async (callbackUrl) => {
438
+ await validate(lease);
439
+ const result = await innerJson(lease.target, target + "/complete", JSON.stringify({ callbackUrl }), lease.cookie, 30_000);
440
+ await validate(lease);
441
+ return JSON.parse(result.bytes.toString("utf8"));
442
+ },
443
+ cancel: async () => {
444
+ await validate(lease);
445
+ return innerJson(lease.target, target, "", lease.cookie, 10_000, "DELETE");
446
+ },
447
+ }, options.publicOrigin());
448
+ res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
449
+ res.end(JSON.stringify(launch));
450
+ return true;
451
+ }
397
452
  await forwardHttp(req, res, route, lease);
398
453
  }
399
454
  catch (error) {
@@ -588,6 +643,7 @@ export function createProjectRuntimeProxy(options) {
588
643
  revokeProject,
589
644
  async close() {
590
645
  closed = true;
646
+ linkBrowser.close();
591
647
  clearInterval(timer);
592
648
  for (const cancel of upgrades)
593
649
  cancel();
@@ -450,6 +450,7 @@ export async function startHeadlessServer(opts) {
450
450
  links = hubAuth
451
451
  ? createLinkHttp({
452
452
  ...configurationOptions,
453
+ browserHandoff: !opts.publicPathPrefix,
453
454
  remoteLink: () => remoteLink,
454
455
  onChanged: () => notify("serve/configurationChanged", {}),
455
456
  })
@@ -563,6 +564,8 @@ export async function startHeadlessServer(opts) {
563
564
  return;
564
565
  }
565
566
  if (hubAuth) {
567
+ if (await links?.handlePublic(req, res))
568
+ return;
566
569
  if (await panels?.handleAssets(req, res))
567
570
  return;
568
571
  if (await hubAuth.handle(req, res))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-server",
3
- "version": "0.9.25",
3
+ "version": "0.9.27",
4
4
  "description": "CodeShell server transport layer — mobile remote HTTP/WS host, pairing, passcode gate, tunnel, rooms, uploads, and session/attachment services. Electron-free; precursor of the auth gateway.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,13 +60,13 @@
60
60
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
61
61
  },
62
62
  "dependencies": {
63
- "@cjhyy/code-shell-capability-coding": "0.9.25",
64
- "@cjhyy/code-shell-core": "0.9.25",
65
- "@cjhyy/code-shell-web": "0.9.25",
63
+ "@cjhyy/code-shell-capability-coding": "0.9.27",
64
+ "@cjhyy/code-shell-core": "0.9.27",
65
+ "@cjhyy/code-shell-web": "0.9.27",
66
66
  "@modelcontextprotocol/sdk": "^1.12.1",
67
67
  "@types/ws": "^8.18.1",
68
68
  "ws": "^8.21.0",
69
- "@cjhyy/code-shell-link": "0.9.25"
69
+ "@cjhyy/code-shell-link": "0.9.27"
70
70
  },
71
71
  "engines": {
72
72
  "node": ">=20.10"