dsh-mobile 0.6.8 → 0.6.10

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/lib/index.mjs CHANGED
@@ -10610,6 +10610,8 @@ var RemoteProviderCoordinator = class {
10610
10610
  store;
10611
10611
  selectedValue;
10612
10612
  queue = Promise.resolve();
10613
+ closing = false;
10614
+ closeTask;
10613
10615
  constructor(selected, controllers, store) {
10614
10616
  this.controllers = controllers;
10615
10617
  this.store = store;
@@ -10661,8 +10663,16 @@ var RemoteProviderCoordinator = class {
10661
10663
  }
10662
10664
  });
10663
10665
  }
10666
+ /** Stop admitting mutations and wait for accepted work before callers release provider resources. */
10667
+ close() {
10668
+ this.closing = true;
10669
+ this.closeTask ??= this.queue;
10670
+ return this.closeTask;
10671
+ }
10664
10672
  enqueue(operation) {
10665
- const task = this.queue.then(() => this.runAndEnforce(operation), () => this.runAndEnforce(operation));
10673
+ if (this.closing) return Promise.reject(/* @__PURE__ */ new Error("remote_provider_closed"));
10674
+ const run = () => this.closing ? Promise.reject(/* @__PURE__ */ new Error("remote_provider_closed")) : this.runAndEnforce(operation);
10675
+ const task = this.queue.then(run, run);
10666
10676
  this.queue = task.then(() => void 0, () => void 0);
10667
10677
  return task;
10668
10678
  }
@@ -13353,6 +13363,8 @@ const MOBILE_CUSTOMIZATION_GUIDE_BODY = `你在为用户定制 DSH Mobile 的手
13353
13363
 
13354
13364
  动手前先读已有的定制文件。若要调整 DSH 自带的输入框、侧栏或设置等现有界面,先确认当前 DSH 版本与手机页面模式(专用移动页面或 ?frontend=stock),只读核对运行页面的 DOM、属性和样式;本机有同版本前端源码时可参考相应组件,没有源码时可查看已安装的客户端产物,不必下载源码。优先使用扩展 API、插槽和稳定的 data-* 标记,不凭猜测依赖构建类名。只读查看不改变前述写入范围。
13355
13365
 
13366
+ 旧 CSS 可能覆盖新版内置修复。先备份并在同一会话、方向和字号下对比有无该样式,不要无症状叠加旧补丁。CSS Modules 的后缀/子串匹配必须限定到已核对的区域祖先,例如 [data-dsh-mobile-header] [class*="_titleRow"];不要裸匹配整页。display: none 会让其中的操作不可用,不得把隐藏功能描述成折叠。
13367
+
13356
13368
  2. 电脑端能力 —— 手机需要读电脑文件、执行命令或访问硬件时,创建扩展:
13357
13369
  - 目录:$DSH_HOME/mobile-access/extensions/<id>/,id 用小写字母数字和连字符(如 media-remote)
13358
13370
  - extension.json:{"schemaVersion":1,"id":"<id>","name":"显示名","version":"0.1.0","description":"说明"}
@@ -13365,6 +13377,8 @@ const MOBILE_CUSTOMIZATION_GUIDE_BODY = `你在为用户定制 DSH Mobile 的手
13365
13377
  - mobile.js 里用 api.host.invoke('动作名', 输入) 调 host.mjs 的 action(请求会按 application/json 发送),api.host.fetch('/路由路径') 调 route,api.host.assetUrl('相对路径') 生成与当前版本绑定的资源地址
13366
13378
  - DSH Web 可先用命令生成模板:dsh plugin --profile web exec dsh-mobile extension create <id> --name "<名称>",再在模板上改;Desktop 请在上方实际定制目录创建文件,不要误改 Web profile
13367
13379
 
13380
+ 附件功能优先复用 DSH 自带的文件上传。扩展若需向输入框模拟文件拖放,dataTransfer 除了 files,还须有可迭代的 items(DataTransferItemList),文件项应提供 kind、type 和 getAsFile();不能只拼一个 files 数组。原生 DataTransfer / DragEvent 可用时优先使用,并通过 items.add(file) 填充文件;还要验证 effectAllowed / dropEffect 能否按投递逻辑读写,不能只检查构造函数是否存在。不完整的 WebView 需要保留这些能力的兼容实现。dragover 接受或事件派发成功不代表附件已加入,须检查 DSH 输入框草稿中的附件与解码后的图片预览;不要用电脑端附件目录的文件数量或修改时间代替这项验证。
13381
+
13368
13382
  安全约束:
13369
13383
  - host.mjs 拥有电脑用户的完整权限,绝不能放入不可信代码,也不要让手机端无条件执行任意命令
13370
13384
  - in-process 的同步阻塞会卡住网关事件循环,超时不能打断;Worker 可约束 JavaScript 阻塞,但不是安全沙箱,不约束全部原生内存,也不回滚副作用。两种模式都应响应 signal,避免编写不可取消的同步循环
@@ -14810,7 +14824,17 @@ var FunnelController = class {
14810
14824
  }
14811
14825
  async failGeneration(generation, code, setupUrl) {
14812
14826
  if (generation !== this.generation) return;
14813
- await this.stopProcessAndGateway();
14827
+ ++this.generation;
14828
+ try {
14829
+ await this.stopProcessAndGateway();
14830
+ } catch (_error) {
14831
+ this.publish({
14832
+ enabled: this.enabled,
14833
+ state: "error",
14834
+ errorCode: "funnel_cleanup_failed"
14835
+ });
14836
+ return;
14837
+ }
14814
14838
  if (this.enabled) this.publish({
14815
14839
  enabled: true,
14816
14840
  state: "error",
@@ -15295,10 +15319,35 @@ async function removeCaddyTree(root, target) {
15295
15319
  }
15296
15320
  //#endregion
15297
15321
  //#region src/caddy-component.ts
15298
- const CADDY_VERSION = "2.11.6";
15322
+ const CADDY_VERSION = "2.11.7";
15299
15323
  const CADDY_DNS_PLUGIN_VERSION = "v0.4.3";
15300
- const CADDY_DOWNLOAD_PAGE = "https://caddyserver.com/download";
15301
- const CADDY_COMPONENT_RELEASES = Object.freeze({});
15324
+ const CADDY_DOWNLOAD_PAGE = "https://github.com/saya-ch/dsh-mobile/releases/tag/caddy-component-2.11.7-tencentcloud-0.4.3-r1";
15325
+ const CADDY_COMPONENT_RELEASES = Object.freeze({
15326
+ "linux-x64": Object.freeze({
15327
+ version: CADDY_VERSION,
15328
+ dnsPluginVersion: CADDY_DNS_PLUGIN_VERSION,
15329
+ platform: "linux",
15330
+ arch: "x64",
15331
+ executableName: "caddy",
15332
+ downloadUrl: "https://github.com/saya-ch/dsh-mobile/releases/download/caddy-component-2.11.7-tencentcloud-0.4.3-r1/caddy-2.11.7-tencentcloud-0.4.3-linux-x64",
15333
+ downloadBytes: 81995704,
15334
+ executableBytes: 81995704,
15335
+ downloadSha256: "13652ef9ea3792ebff772a5b873ade63ed6de1c0a794bb3403a523639b8f57af",
15336
+ executableSha256: "13652ef9ea3792ebff772a5b873ade63ed6de1c0a794bb3403a523639b8f57af"
15337
+ }),
15338
+ "win32-x64": Object.freeze({
15339
+ version: CADDY_VERSION,
15340
+ dnsPluginVersion: CADDY_DNS_PLUGIN_VERSION,
15341
+ platform: "win32",
15342
+ arch: "x64",
15343
+ executableName: "caddy.exe",
15344
+ downloadUrl: "https://github.com/saya-ch/dsh-mobile/releases/download/caddy-component-2.11.7-tencentcloud-0.4.3-r1/caddy-2.11.7-tencentcloud-0.4.3-win32-x64.exe",
15345
+ downloadBytes: 82816e3,
15346
+ executableBytes: 82816e3,
15347
+ downloadSha256: "962f7937a06e1e737513d2d1d4ad75df54ba503d36fa2a458912bd38fbd0094a",
15348
+ executableSha256: "962f7937a06e1e737513d2d1d4ad75df54ba503d36fa2a458912bd38fbd0094a"
15349
+ })
15350
+ });
15302
15351
  Object.freeze({
15303
15352
  version: CADDY_VERSION,
15304
15353
  dnsPluginVersion: CADDY_DNS_PLUGIN_VERSION
@@ -15322,14 +15371,16 @@ function caddyInspectionEnvironment(environment = process.env) {
15322
15371
  ]) if (environment[name] !== void 0) result[name] = environment[name];
15323
15372
  return result;
15324
15373
  }
15325
- async function inspect(executable) {
15374
+ async function inspect(executable, assertOpen) {
15326
15375
  const options = {
15327
15376
  env: caddyInspectionEnvironment(),
15328
15377
  timeout: 15e3,
15329
15378
  maxBuffer: 262144
15330
15379
  };
15380
+ const version = (await execFileText(executable, ["version"], options)).stdout.trim();
15381
+ assertOpen();
15331
15382
  return {
15332
- version: (await execFileText(executable, ["version"], options)).stdout.trim(),
15383
+ version,
15333
15384
  modules: (await execFileText(executable, ["list-modules", "--versions"], options)).stdout
15334
15385
  };
15335
15386
  }
@@ -15348,6 +15399,10 @@ var CaddyComponentManager = class {
15348
15399
  installed = false;
15349
15400
  errorCode;
15350
15401
  queue = Promise.resolve();
15402
+ closed = false;
15403
+ closing;
15404
+ download;
15405
+ cleanupPaths = /* @__PURE__ */ new Set();
15351
15406
  constructor(options) {
15352
15407
  if (!isAbsolute(options.stateDirectory)) throw new Error("caddy state directory must be absolute");
15353
15408
  this.stateDirectory = resolve(options.stateDirectory);
@@ -15357,7 +15412,7 @@ var CaddyComponentManager = class {
15357
15412
  if (artifact !== void 0 && (artifact.platform !== platform || artifact.arch !== arch || !/^https:\/\//u.test(artifact.downloadUrl) || !/^[a-f0-9]{64}$/u.test(artifact.downloadSha256) || !/^[a-f0-9]{64}$/u.test(artifact.executableSha256) || artifact.downloadBytes < 1 || artifact.executableBytes < 1 || !/^[a-z0-9.-]+$/u.test(artifact.executableName))) throw new Error("caddy_artifact_invalid");
15358
15413
  this.artifact = artifact;
15359
15414
  this.componentRoot = join(this.stateDirectory, "components", "caddy");
15360
- this.componentStorage = join(this.componentRoot, artifact?.version ?? "2.11.6");
15415
+ this.componentStorage = join(this.componentRoot, artifact?.version ?? "2.11.7");
15361
15416
  this.executable = join(this.componentStorage, artifact?.executableName ?? (platform === "win32" ? "caddy.exe" : "caddy"));
15362
15417
  this.logRoot = join(this.stateDirectory, "caddy", "logs");
15363
15418
  this.stagingRoot = join(this.stateDirectory, "staging", "caddy");
@@ -15367,41 +15422,48 @@ var CaddyComponentManager = class {
15367
15422
  expectedBytes: artifact?.downloadBytes ?? 0,
15368
15423
  errorPrefix: "caddy"
15369
15424
  }));
15370
- this.inspectExecutable = options.inspectExecutable ?? inspect;
15425
+ this.inspectExecutable = options.inspectExecutable ?? ((executable) => inspect(executable, () => this.assertOpen()));
15371
15426
  this.promote = options.promoteDirectory ?? ((source, destination) => renameCaddyPath(this.stateDirectory, source, destination));
15372
15427
  }
15373
15428
  /** Recheck a pinned executable before launching; no unverified local binary can run. */
15374
- async ensureExecutable() {
15429
+ ensureExecutable() {
15430
+ return this.enqueue(() => this.checkExecutable());
15431
+ }
15432
+ async checkExecutable() {
15375
15433
  const artifact = this.artifact;
15376
15434
  if (artifact === void 0) throw new Error("caddy_component_unavailable");
15377
15435
  await assertCaddyParents(this.stateDirectory, this.executable);
15436
+ this.assertOpen();
15378
15437
  const entry = await lstat(this.executable);
15379
15438
  if (!entry.isFile() || entry.isSymbolicLink() || entry.size !== artifact.executableBytes || await digest(this.executable) !== artifact.executableSha256) throw new Error("caddy_component_invalid");
15380
15439
  await this.verifyMetadata(this.executable, artifact);
15381
15440
  }
15382
15441
  async initialize() {
15383
- this.installed = false;
15384
- this.errorCode = void 0;
15385
- if (this.artifact === void 0) {
15386
- this.errorCode = "caddy_component_unavailable";
15387
- return;
15388
- }
15389
- try {
15390
- await this.ensureExecutable();
15391
- this.installed = true;
15392
- } catch (error) {
15393
- if (error.code !== "ENOENT") this.errorCode = "caddy_component_invalid";
15394
- }
15442
+ return this.enqueue(async () => {
15443
+ this.installed = false;
15444
+ this.errorCode = void 0;
15445
+ if (this.artifact === void 0) {
15446
+ this.errorCode = "caddy_component_unavailable";
15447
+ return;
15448
+ }
15449
+ try {
15450
+ await this.checkExecutable();
15451
+ this.installed = true;
15452
+ } catch (error) {
15453
+ this.assertOpen();
15454
+ if (error.code !== "ENOENT") this.errorCode = "caddy_component_invalid";
15455
+ }
15456
+ });
15395
15457
  }
15396
15458
  status() {
15397
15459
  const artifact = this.artifact;
15398
15460
  return Object.freeze({
15399
15461
  supported: artifact !== void 0,
15400
15462
  installed: this.installed,
15401
- version: artifact?.version ?? "2.11.6",
15463
+ version: artifact?.version ?? "2.11.7",
15402
15464
  downloadBytes: artifact?.downloadBytes ?? 0,
15403
15465
  installedBytes: this.installed ? artifact?.executableBytes ?? 0 : 0,
15404
- sourceUrl: artifact?.downloadUrl ?? "https://caddyserver.com/download",
15466
+ sourceUrl: artifact?.downloadUrl ?? "https://github.com/saya-ch/dsh-mobile/releases/tag/caddy-component-2.11.7-tencentcloud-0.4.3-r1",
15405
15467
  downloadPage: CADDY_DOWNLOAD_PAGE,
15406
15468
  storagePath: this.componentRoot,
15407
15469
  ...this.errorCode === void 0 ? {} : { errorCode: this.errorCode }
@@ -15413,28 +15475,40 @@ var CaddyComponentManager = class {
15413
15475
  const artifact = this.artifact;
15414
15476
  if (artifact === void 0) throw new Error("caddy_component_unavailable");
15415
15477
  await ensureCaddyDirectory(this.stateDirectory, this.stagingRoot);
15478
+ this.assertOpen();
15416
15479
  await ensureCaddyDirectory(this.stateDirectory, this.componentRoot);
15480
+ this.assertOpen();
15417
15481
  const staging = await mkdtemp(join(this.stagingRoot, "install-"));
15418
15482
  const candidate = join(this.componentRoot, ".install-" + randomBytes(12).toString("hex"));
15419
15483
  const backup = join(this.componentRoot, ".previous-" + randomBytes(12).toString("hex"));
15420
15484
  let previous = false;
15421
15485
  let promoted = false;
15422
15486
  let failed = false;
15487
+ let operationError;
15423
15488
  try {
15489
+ this.assertOpen();
15424
15490
  const controller = new AbortController();
15425
15491
  const timeout = setTimeout(() => {
15426
15492
  controller.abort();
15427
15493
  }, 6e5);
15428
15494
  timeout.unref();
15495
+ this.download = {
15496
+ controller,
15497
+ timeout
15498
+ };
15429
15499
  let bytes;
15430
15500
  try {
15431
15501
  bytes = await this.fetchArtifact(artifact.downloadUrl, controller.signal);
15432
15502
  } finally {
15433
15503
  clearTimeout(timeout);
15504
+ this.download = void 0;
15434
15505
  }
15506
+ this.assertOpen();
15507
+ controller.signal.throwIfAborted();
15435
15508
  if (bytes.byteLength !== artifact.downloadBytes) throw new Error("caddy_download_size_mismatch");
15436
15509
  if (createHash("sha256").update(bytes).digest("hex") !== artifact.downloadSha256) throw new Error("caddy_download_hash_mismatch");
15437
15510
  await ensureCaddyDirectory(this.stateDirectory, candidate);
15511
+ this.assertOpen();
15438
15512
  const executable = join(candidate, artifact.executableName);
15439
15513
  await writeFile(executable, bytes, {
15440
15514
  flag: "wx",
@@ -15443,8 +15517,10 @@ var CaddyComponentManager = class {
15443
15517
  await chmod(executable, 448);
15444
15518
  if (bytes.byteLength !== artifact.executableBytes || await digest(executable) !== artifact.executableSha256) throw new Error("caddy_executable_hash_mismatch");
15445
15519
  await this.verifyMetadata(executable, artifact);
15520
+ this.assertOpen();
15446
15521
  try {
15447
15522
  const entry = await lstat(this.componentStorage);
15523
+ this.assertOpen();
15448
15524
  if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error("caddy_path_invalid");
15449
15525
  await renameCaddyPath(this.stateDirectory, this.componentStorage, backup);
15450
15526
  previous = true;
@@ -15452,6 +15528,7 @@ var CaddyComponentManager = class {
15452
15528
  if (error.code !== "ENOENT") throw error;
15453
15529
  }
15454
15530
  try {
15531
+ this.assertOpen();
15455
15532
  await this.promote(candidate, this.componentStorage);
15456
15533
  promoted = true;
15457
15534
  } catch (error) {
@@ -15469,21 +15546,29 @@ var CaddyComponentManager = class {
15469
15546
  this.errorCode = void 0;
15470
15547
  } catch (error) {
15471
15548
  failed = true;
15549
+ operationError = error;
15472
15550
  throw error;
15473
15551
  } finally {
15474
- let cleanupError;
15552
+ const cleanupErrors = [];
15475
15553
  for (const path of [
15476
15554
  staging,
15477
15555
  candidate,
15478
15556
  ...previous && promoted ? [backup] : []
15479
- ]) try {
15480
- await removeCaddyTree(this.stateDirectory, path);
15481
- } catch (error) {
15482
- cleanupError ??= error;
15557
+ ]) {
15558
+ this.cleanupPaths.add(path);
15559
+ try {
15560
+ await removeCaddyTree(this.stateDirectory, path);
15561
+ this.cleanupPaths.delete(path);
15562
+ } catch (error) {
15563
+ cleanupErrors.push(error);
15564
+ }
15565
+ }
15566
+ if (cleanupErrors.length > 0) {
15567
+ if (!failed && cleanupErrors.length === 1) throw cleanupErrors[0];
15568
+ throw new AggregateError([...failed ? [operationError] : [], ...cleanupErrors], "caddy_component_cleanup_failed", failed ? { cause: operationError } : void 0);
15483
15569
  }
15484
- if (!failed && cleanupError !== void 0) throw cleanupError;
15485
15570
  }
15486
- });
15571
+ }).then(() => this.status());
15487
15572
  }
15488
15573
  /** Remove optional component files; callers stop the controller and purge runtime configuration separately. */
15489
15574
  purge() {
@@ -15492,18 +15577,57 @@ var CaddyComponentManager = class {
15492
15577
  await removeCaddyTree(this.stateDirectory, this.stagingRoot);
15493
15578
  this.installed = false;
15494
15579
  this.errorCode = this.artifact === void 0 ? "caddy_component_unavailable" : void 0;
15580
+ }).then(() => this.status());
15581
+ }
15582
+ /**
15583
+ * Permanently stop admission and abort downloads, then await admitted work and cleanup.
15584
+ * An already started promotion/purge settles normally; cancellation does not undo committed bytes.
15585
+ * A downloader that ignores abort remains owned until its promise settles. Failed temporary cleanup
15586
+ * is retried here; a failed close can be called again without reopening admission or deleting recovery backups.
15587
+ */
15588
+ close() {
15589
+ if (this.closing !== void 0) return this.closing;
15590
+ this.closed = true;
15591
+ if (this.download !== void 0) {
15592
+ clearTimeout(this.download.timeout);
15593
+ this.download.controller.abort(/* @__PURE__ */ new Error("caddy_component_closed"));
15594
+ }
15595
+ const task = this.queue.then(async () => {
15596
+ const errors = [];
15597
+ for (const path of this.cleanupPaths) try {
15598
+ await removeCaddyTree(this.stateDirectory, path);
15599
+ this.cleanupPaths.delete(path);
15600
+ } catch (error) {
15601
+ errors.push(error);
15602
+ }
15603
+ if (errors.length === 1) throw errors[0];
15604
+ if (errors.length > 1) throw new AggregateError(errors, "caddy_component_cleanup_failed");
15495
15605
  });
15606
+ this.closing = task;
15607
+ task.catch(() => {
15608
+ if (this.closing === task) this.closing = void 0;
15609
+ });
15610
+ return task;
15496
15611
  }
15497
15612
  async verifyMetadata(executable, artifact) {
15613
+ this.assertOpen();
15498
15614
  const info = await this.inspectExecutable(executable);
15615
+ this.assertOpen();
15499
15616
  if (!info.version.startsWith("v" + artifact.version + " ")) throw new Error("caddy_executable_version_mismatch");
15500
15617
  const nonstandard = info.modules.split(/\r?\n/u).filter((line) => line.startsWith("dns.providers."));
15501
15618
  if (nonstandard.length !== 1 || nonstandard[0] !== "dns.providers.tencentcloud " + artifact.dnsPluginVersion || !info.modules.includes("Non-standard modules: 1")) throw new Error("caddy_executable_modules_mismatch");
15502
15619
  }
15620
+ assertOpen() {
15621
+ if (this.closed) throw new Error("caddy_component_closed");
15622
+ }
15503
15623
  enqueue(operation) {
15504
- const task = this.queue.then(operation, operation);
15624
+ if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("caddy_component_closed"));
15625
+ const task = this.queue.then(() => {
15626
+ this.assertOpen();
15627
+ return operation();
15628
+ });
15505
15629
  this.queue = task.then(() => void 0, () => void 0);
15506
- return task.then(() => this.status());
15630
+ return task;
15507
15631
  }
15508
15632
  };
15509
15633
  const MAX_CONFIG_BYTES = 16384;
@@ -15862,6 +15986,7 @@ var CaddyController = class {
15862
15986
  } catch {}
15863
15987
  }
15864
15988
  async start() {
15989
+ if (this.child !== void 0 || this.gatewayValue !== void 0) await this.stop();
15865
15990
  const generation = ++this.generation;
15866
15991
  const startup = new AbortController();
15867
15992
  this.startupAbort = startup;
@@ -15911,12 +16036,19 @@ var CaddyController = class {
15911
16036
  "--adapter",
15912
16037
  "caddyfile"
15913
16038
  ], environment);
15914
- this.child = child;
16039
+ const ownedChild = {
16040
+ process: child,
16041
+ closed: false
16042
+ };
16043
+ this.child = ownedChild;
16044
+ child.once("close", () => {
16045
+ ownedChild.closed = true;
16046
+ });
15915
16047
  child.stdout.resume();
15916
16048
  child.stderr.resume();
15917
16049
  child.stdin.end();
15918
16050
  const failed = (code) => {
15919
- if (generation !== this.generation || this.child !== child) return;
16051
+ if (generation !== this.generation || this.child !== ownedChild) return;
15920
16052
  childFailure = code;
15921
16053
  startup.abort();
15922
16054
  if (this.latest.state === "ready") this.enqueue(async () => {
@@ -15991,20 +16123,49 @@ var CaddyController = class {
15991
16123
  this.startupAbort?.abort();
15992
16124
  await this.stopResources();
15993
16125
  }
16126
+ waitForChildClose(child) {
16127
+ if (child.closed) return Promise.resolve();
16128
+ return new Promise((resolve, reject) => {
16129
+ const finish = (error) => {
16130
+ clearTimeout(timer);
16131
+ child.process.removeListener("close", closed);
16132
+ if (error === void 0) resolve();
16133
+ else reject(error);
16134
+ };
16135
+ const closed = () => {
16136
+ finish();
16137
+ };
16138
+ const timer = setTimeout(() => {
16139
+ finish(/* @__PURE__ */ new Error("caddy_process_close_timeout"));
16140
+ }, 1500);
16141
+ timer.unref();
16142
+ child.process.once("close", closed);
16143
+ });
16144
+ }
15994
16145
  async stopResources() {
15995
16146
  const child = this.child;
15996
16147
  const gateway = this.gatewayValue;
15997
- await settleRemoteResources([async () => {
15998
- if (child !== void 0) {
15999
- await (this.options.terminateProcess ?? terminateRemoteProcess)(child);
16000
- if (this.child === child) this.child = void 0;
16001
- }
16002
- }, async () => {
16003
- if (gateway !== void 0) {
16004
- await gateway.close();
16005
- if (this.gatewayValue === gateway) this.gatewayValue = void 0;
16006
- }
16007
- }], "caddy cleanup failed");
16148
+ try {
16149
+ await settleRemoteResources([async () => {
16150
+ if (child !== void 0) {
16151
+ await (this.options.terminateProcess ?? terminateRemoteProcess)(child.process);
16152
+ await this.waitForChildClose(child);
16153
+ if (this.child === child) this.child = void 0;
16154
+ }
16155
+ }, async () => {
16156
+ if (gateway !== void 0) {
16157
+ await gateway.close();
16158
+ if (this.gatewayValue === gateway) this.gatewayValue = void 0;
16159
+ }
16160
+ }], "caddy cleanup failed");
16161
+ } catch (error) {
16162
+ this.publish({
16163
+ enabled: this.enabled,
16164
+ state: "error",
16165
+ errorCode: "caddy_cleanup_failed"
16166
+ });
16167
+ throw error;
16168
+ }
16008
16169
  }
16009
16170
  };
16010
16171
  //#endregion
@@ -18020,6 +18181,11 @@ async function apply(ctx, config) {
18020
18181
  adminClosing = true;
18021
18182
  unregister();
18022
18183
  };
18184
+ const drainRemoteWork = async () => {
18185
+ const draining = remoteProviders.close();
18186
+ caddyController.cancelPendingStart();
18187
+ await settleCleanupSteps([() => caddyComponent.close(), () => draining]);
18188
+ };
18023
18189
  const disposeMobileCommand = ctx.commands.register({
18024
18190
  name: "mobile",
18025
18191
  description: "定制手机界面或添加电脑能力",
@@ -18086,6 +18252,7 @@ async function apply(ctx, config) {
18086
18252
  try {
18087
18253
  await settleCleanupSteps([
18088
18254
  stopAdmin,
18255
+ drainRemoteWork,
18089
18256
  () => trustedNetworksWrite,
18090
18257
  disposeMobileCommand,
18091
18258
  disposeTaskEvents,
@@ -18105,6 +18272,7 @@ async function apply(ctx, config) {
18105
18272
  return async () => {
18106
18273
  await settleCleanupSteps([
18107
18274
  stopAdmin,
18275
+ drainRemoteWork,
18108
18276
  () => trustedNetworksWrite,
18109
18277
  disposeMobileCommand,
18110
18278
  disposeTaskEvents,