@cjhyy/code-shell-server 0.9.35 → 0.9.37

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.
Files changed (32) hide show
  1. package/dist/bin/code-shell-cloud-account.d.ts +2 -0
  2. package/dist/bin/code-shell-cloud-account.js +53 -0
  3. package/dist/index.serve.d.ts +3 -0
  4. package/dist/index.serve.js +2 -0
  5. package/dist/mobile-remote/mobile-client-event-validator.js +10 -1
  6. package/dist/mobile-remote/room-history.d.ts +20 -0
  7. package/dist/mobile-remote/room-history.js +327 -0
  8. package/dist/mobile-remote/room-manager.d.ts +11 -0
  9. package/dist/mobile-remote/room-manager.js +107 -11
  10. package/dist/panels/management.js +12 -1
  11. package/dist/panels/runtime-services.js +32 -7
  12. package/dist/panels/types.d.ts +5 -3
  13. package/dist/project-runtime/account-adoption.d.ts +34 -0
  14. package/dist/project-runtime/account-adoption.js +280 -0
  15. package/dist/project-runtime/account-binding.d.ts +12 -0
  16. package/dist/project-runtime/account-binding.js +33 -0
  17. package/dist/project-runtime/backup.d.ts +7 -0
  18. package/dist/project-runtime/backup.js +56 -8
  19. package/dist/project-runtime/cloud-account-auth.d.ts +23 -0
  20. package/dist/project-runtime/cloud-account-auth.js +287 -0
  21. package/dist/project-runtime/control-authority.d.ts +46 -0
  22. package/dist/project-runtime/control-authority.js +15 -0
  23. package/dist/project-runtime/control-server.d.ts +3 -0
  24. package/dist/project-runtime/control-server.js +60 -24
  25. package/dist/project-runtime/lock.js +181 -12
  26. package/dist/project-runtime/manager.d.ts +12 -8
  27. package/dist/project-runtime/manager.js +56 -15
  28. package/dist/project-runtime/proxy.d.ts +3 -3
  29. package/dist/project-runtime/proxy.js +4 -1
  30. package/dist/project-runtime/registry.d.ts +14 -1
  31. package/dist/project-runtime/registry.js +91 -2
  32. package/package.json +7 -6
@@ -2,6 +2,7 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
  import { constants, chmodSync, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { acquireProjectControllerLock } from "./lock.js";
5
+ import { CLOUD_ADOPTION_PENDING, isCloudAccountBinding, } from "./account-binding.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 TOKEN = /^[A-Za-z0-9_-]{43}$/;
7
8
  const STATUSES = new Set(["stopped", "starting", "running", "stopping", "error"]);
@@ -58,6 +59,7 @@ export class ProjectRegistry {
58
59
  controllerLock;
59
60
  diskDigest;
60
61
  markerEstablished = false;
62
+ storageFailed = false;
61
63
  constructor(dataDir, now = Date.now, io = {}) {
62
64
  this.now = now;
63
65
  this.io = io;
@@ -72,6 +74,16 @@ export class ProjectRegistry {
72
74
  this.marker = join(this.directory, "initialized");
73
75
  this.controllerLock = acquireProjectControllerLock(this.directory);
74
76
  try {
77
+ if (!this.io.offlineAccountAdoption) {
78
+ try {
79
+ lstatSync(join(this.directory, CLOUD_ADOPTION_PENDING));
80
+ throw new Error("Cloud account adoption is incomplete; retry its explicit offline transaction.");
81
+ }
82
+ catch (error) {
83
+ if (error.code !== "ENOENT")
84
+ throw error;
85
+ }
86
+ }
75
87
  if (existsSync(this.marker)) {
76
88
  if (privateFileBytes(this.marker, 16).toString() !== "1\n")
77
89
  throw new Error("Project initialization marker is corrupt.");
@@ -109,6 +121,8 @@ export class ProjectRegistry {
109
121
  throw new Error("Stop all projects before restoring an installation.");
110
122
  const next = structuredClone(this.data);
111
123
  next.installationId = randomUUID();
124
+ if (next.accountBinding)
125
+ next.accountBinding.managementEpoch = randomUUID();
112
126
  for (const project of next.projects) {
113
127
  if (project.generation > 0)
114
128
  project.generation += 1;
@@ -122,15 +136,43 @@ export class ProjectRegistry {
122
136
  this.assertStorage();
123
137
  return this.data.installationId;
124
138
  }
139
+ get accountBinding() {
140
+ this.assertStorage();
141
+ return this.data.accountBinding && { ...this.data.accountBinding };
142
+ }
143
+ /** Offline migration only; the caller must durably preserve source evidence first. */
144
+ bindAccount(binding) {
145
+ this.assertStorage();
146
+ if (this.data.version !== 1 ||
147
+ this.data.accountBinding !== undefined ||
148
+ !isCloudAccountBinding(binding) ||
149
+ this.data.owners.length !== 1 ||
150
+ this.data.owners[0].id !== binding.ownerId ||
151
+ this.data.projects.some((project) => project.status !== "stopped"))
152
+ throw new Error("Cloud adoption requires one explicit legacy owner and stopped projects.");
153
+ const next = structuredClone(this.data);
154
+ // The real historical registry rejects version 2. Unknown metadata alone
155
+ // cannot prevent an old controller from continuing its username authority.
156
+ next.version = 2;
157
+ next.accountBinding = { ...binding };
158
+ this.write(next);
159
+ this.data = next;
160
+ }
125
161
  validate(data = this.data) {
126
162
  if (!data ||
127
- data.version !== 1 ||
163
+ (data.version !== 1 && data.version !== 2) ||
128
164
  !UUID.test(data.installationId) ||
129
165
  !Array.isArray(data.owners) ||
130
166
  !Array.isArray(data.projects) ||
131
167
  data.owners.length > 128 ||
132
168
  data.projects.length > 128)
133
169
  throw new Error("Project registry is corrupt; restore its backup");
170
+ if ((data.version === 1 && data.accountBinding !== undefined) ||
171
+ (data.version === 2 &&
172
+ (!isCloudAccountBinding(data.accountBinding) ||
173
+ data.owners.length !== 1 ||
174
+ data.owners[0]?.id !== data.accountBinding.ownerId)))
175
+ throw new Error("Cloud account ownership is missing or corrupt; restore its backup.");
134
176
  const owners = new Set();
135
177
  const names = new Set();
136
178
  for (const owner of data.owners) {
@@ -174,6 +216,8 @@ export class ProjectRegistry {
174
216
  }
175
217
  }
176
218
  assertStorage() {
219
+ if (this.storageFailed)
220
+ throw new Error("Project registry durability failed; restart and diagnose before continuing.");
177
221
  this.controllerLock.assertHeld();
178
222
  if (this.markerEstablished && privateFileBytes(this.marker, 16).toString() !== "1\n")
179
223
  throw new Error("Project initialization marker changed.");
@@ -192,15 +236,29 @@ export class ProjectRegistry {
192
236
  throw new Error("Project registry is too large.");
193
237
  const file = join(this.directory, `.registry-${randomUUID()}.tmp`);
194
238
  let fd;
239
+ let committed = false;
195
240
  try {
196
241
  fd = openSync(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
197
242
  writeFileSync(fd, bytes);
198
- fsyncSync(fd);
243
+ (this.io.fsync ?? fsyncSync)(fd);
199
244
  closeSync(fd);
200
245
  fd = undefined;
201
246
  this.assertStorage();
202
247
  (this.io.rename ?? renameSync)(file, this.file);
248
+ committed = true;
203
249
  this.diskDigest = digest(bytes);
250
+ const directory = openSync(this.directory, constants.O_RDONLY);
251
+ try {
252
+ (this.io.fsync ?? fsyncSync)(directory);
253
+ }
254
+ finally {
255
+ closeSync(directory);
256
+ }
257
+ }
258
+ catch (error) {
259
+ if (committed)
260
+ this.storageFailed = true;
261
+ throw error;
204
262
  }
205
263
  finally {
206
264
  if (fd !== undefined)
@@ -214,11 +272,13 @@ export class ProjectRegistry {
214
272
  }
215
273
  list(username) {
216
274
  this.assertStorage();
275
+ this.requireLegacy();
217
276
  const owner = this.data.owners.find((item) => item.username === username);
218
277
  return this.data.projects.filter((item) => item.ownerId === owner?.id).map(projectView);
219
278
  }
220
279
  get(username, id) {
221
280
  this.assertStorage();
281
+ this.requireLegacy();
222
282
  const owner = this.data.owners.find((item) => item.username === username);
223
283
  const project = this.data.projects.find((item) => item.id === id && item.ownerId === owner?.id);
224
284
  if (!project)
@@ -227,6 +287,35 @@ export class ProjectRegistry {
227
287
  }
228
288
  create(username, name) {
229
289
  this.assertStorage();
290
+ this.requireLegacy();
291
+ return this.createForOwner(username, name);
292
+ }
293
+ listAccount(ownerId) {
294
+ this.requireAccountOwner(ownerId);
295
+ return this.data.projects.filter((item) => item.ownerId === ownerId).map(projectView);
296
+ }
297
+ getAccount(ownerId, id) {
298
+ this.requireAccountOwner(ownerId);
299
+ const project = this.data.projects.find((item) => item.id === id && item.ownerId === ownerId);
300
+ if (!project)
301
+ throw new ProjectRegistryError(404, "找不到这个项目。");
302
+ return { ...project };
303
+ }
304
+ createAccount(ownerId, name) {
305
+ this.requireAccountOwner(ownerId);
306
+ // This username is internal legacy metadata, never the Account display name.
307
+ return this.createForOwner(this.data.owners[0].username, name);
308
+ }
309
+ requireLegacy() {
310
+ if (this.data.version !== 1)
311
+ throw new ProjectRegistryError(403, "此 Cloud 已采用明确的账号归属,请通过云账号管理。");
312
+ }
313
+ requireAccountOwner(ownerId) {
314
+ this.assertStorage();
315
+ if (this.data.version !== 2 || this.data.accountBinding?.ownerId !== ownerId)
316
+ throw new ProjectRegistryError(403, "此账号没有 Cloud 管理权限。");
317
+ }
318
+ createForOwner(username, name) {
230
319
  if (typeof name !== "string" ||
231
320
  !name.trim() ||
232
321
  name.trim().length > 80 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-server",
3
- "version": "0.9.35",
3
+ "version": "0.9.37",
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",
@@ -49,7 +49,8 @@
49
49
  },
50
50
  "bin": {
51
51
  "code-shell-serve": "./dist/bin/code-shell-serve.js",
52
- "code-shell-settings-recovery": "./dist/bin/code-shell-settings-recovery.js"
52
+ "code-shell-settings-recovery": "./dist/bin/code-shell-settings-recovery.js",
53
+ "code-shell-cloud-account": "./dist/bin/code-shell-cloud-account.js"
53
54
  },
54
55
  "files": [
55
56
  "dist"
@@ -60,13 +61,13 @@
60
61
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
61
62
  },
62
63
  "dependencies": {
63
- "@cjhyy/code-shell-capability-coding": "0.9.35",
64
- "@cjhyy/code-shell-core": "0.9.35",
65
- "@cjhyy/code-shell-web": "0.9.35",
64
+ "@cjhyy/code-shell-capability-coding": "0.9.37",
65
+ "@cjhyy/code-shell-core": "0.9.37",
66
+ "@cjhyy/code-shell-web": "0.9.37",
66
67
  "@modelcontextprotocol/sdk": "^1.31.0",
67
68
  "@types/ws": "^8.18.1",
68
69
  "ws": "^8.21.0",
69
- "@cjhyy/code-shell-link": "0.9.35"
70
+ "@cjhyy/code-shell-link": "0.9.37"
70
71
  },
71
72
  "engines": {
72
73
  "node": ">=20.10"