@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.
- package/dist/links/browser-handoff.d.ts +16 -0
- package/dist/links/browser-handoff.js +150 -0
- package/dist/links/http.d.ts +8 -2
- package/dist/links/http.js +63 -4
- package/dist/links/remote-catalog.d.ts +94 -0
- package/dist/links/remote-catalog.js +90 -0
- package/dist/links/service.d.ts +8 -1
- package/dist/links/service.js +558 -76
- package/dist/links/types.d.ts +1 -1
- package/dist/project-runtime/proxy.js +60 -4
- package/dist/serve/headless-server.js +3 -0
- package/package.json +5 -5
package/dist/links/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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 (
|
|
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.
|
|
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.
|
|
64
|
-
"@cjhyy/code-shell-core": "0.9.
|
|
65
|
-
"@cjhyy/code-shell-web": "0.9.
|
|
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.
|
|
69
|
+
"@cjhyy/code-shell-link": "0.9.27"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
72
72
|
"node": ">=20.10"
|