@linxin666/dsh-ssh 0.4.3 → 0.4.4

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/client.js CHANGED
@@ -115,6 +115,7 @@ window.__ModuleLoader__.load({
115
115
  "terminal.ready": "终端已连接({alias})",
116
116
  "terminal.exited": "终端已退出({alias})",
117
117
  "terminal.error": "终端错误:{error}",
118
+ "terminal.noWebSocket": "当前页面不支持 WebSocket(应用外壳只转发 HTTP),请改用浏览器打开的 Web 界面使用终端。",
118
119
  "terminal.auth.title": "二次身份验证 (2FA / 交互式认证)",
119
120
  "terminal.auth.submit": "提交验证码",
120
121
  "terminal.auth.cancel": "取消",
@@ -270,6 +271,7 @@ window.__ModuleLoader__.load({
270
271
  "terminal.ready": "Terminal connected ({alias})",
271
272
  "terminal.exited": "Terminal exited ({alias})",
272
273
  "terminal.error": "Terminal error: {error}",
274
+ "terminal.noWebSocket": "This page cannot carry a WebSocket (the application shell forwards HTTP only). Open the Web UI in a browser to use the terminal.",
273
275
  "terminal.auth.title": "Two-Factor Authentication (2FA)",
274
276
  "terminal.auth.submit": "Submit Code",
275
277
  "terminal.auth.cancel": "Cancel",
@@ -447,6 +449,63 @@ window.__ModuleLoader__.load({
447
449
  const text = search.toString();
448
450
  return text === "" ? "" : "?" + text;
449
451
  }
452
+ /**
453
+ * The WebSocket URL the terminal route is reached at, or undefined when this
454
+ * page cannot carry one.
455
+ *
456
+ * A page delivered by an application on this machine (the official DSH
457
+ * Desktop shell serves its Web GUI from `dsh-app://app/`) has no WebSocket
458
+ * transport: its scheme handler forwards HTTP requests to the local host but
459
+ * cannot upgrade a socket, so `ws://app/...` never connects and the terminal
460
+ * reports "connection error" (issue #1744). Every other page is a web page and
461
+ * resolves the socket against its own origin, exactly as before.
462
+ *
463
+ * A blank authority is treated the same as an application scheme: there is
464
+ * nothing to dial, and the caller gets the actionable reason instead of a
465
+ * socket that can only fail.
466
+ *
467
+ * @param location - the page location to read.
468
+ * @param search - the query string carrying `alias` or `session`.
469
+ * @returns the absolute `ws:`/`wss:` URL, or undefined when the page cannot carry one.
470
+ */
471
+ function terminalSocketUrl(location, search) {
472
+ if (WEB_PAGE_PROTOCOLS.includes(location.protocol)) return (location.protocol === "https:" ? "wss" : "ws") + "://" + location.host + SSH_API.terminal + search;
473
+ }
474
+ /**
475
+ * Schemes a network page can be delivered with. Every other scheme belongs to
476
+ * an application on this machine, which is the same classification the remote
477
+ * channel and the update seat use (`isWebPageProtocol` / `isApplicationDeliveredPage`).
478
+ */
479
+ const WEB_PAGE_PROTOCOLS = [
480
+ "http:",
481
+ "https:",
482
+ "blob:",
483
+ "data:",
484
+ "about:",
485
+ "filesystem:"
486
+ ];
487
+ /**
488
+ * A terminal connection that never opened: it reports one failure once the
489
+ * view has attached its `onExit`, so the tab shows why instead of sitting on a
490
+ * spinner, and every later frame is a no-op.
491
+ */
492
+ function failedTerminal(reason) {
493
+ const connection = {
494
+ onReady: void 0,
495
+ onOutput: void 0,
496
+ onExit: void 0,
497
+ onAuthPrompt: void 0,
498
+ send: () => void 0,
499
+ resize: () => void 0,
500
+ sendAuthResponse: () => void 0,
501
+ detach: () => void 0,
502
+ close: () => void 0
503
+ };
504
+ queueMicrotask(() => {
505
+ connection.onExit?.(null, reason);
506
+ });
507
+ return connection;
508
+ }
450
509
  /** The browser half's only data entry point. */
451
510
  var SshApi = class {
452
511
  async listHosts(queryText) {
@@ -674,8 +733,14 @@ window.__ModuleLoader__.load({
674
733
  }
675
734
  /** One terminal socket over either an alias (open) or a session id (attach). */
676
735
  terminalSocket(search) {
677
- const url = (window.location.protocol === "https:" ? "wss" : "ws") + "://" + window.location.host + SSH_API.terminal + search;
678
- const socket = new WebSocket(url);
736
+ const target = terminalSocketUrl(window.location, search);
737
+ if (target === void 0) return failedTerminal(tt("terminal.noWebSocket"));
738
+ let socket;
739
+ try {
740
+ socket = new WebSocket(target);
741
+ } catch (error) {
742
+ return failedTerminal(error instanceof Error ? error.message : String(error));
743
+ }
679
744
  let leaving = false;
680
745
  const sendFrame = (frame) => {
681
746
  if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify(frame));
@@ -16302,7 +16367,7 @@ window.__ModuleLoader__.load({
16302
16367
  /** The building package's version, when the bundle carries it. */
16303
16368
  function bakedVersion() {
16304
16369
  try {
16305
- return "0.4.3";
16370
+ return "0.4.4";
16306
16371
  } catch {
16307
16372
  return;
16308
16373
  }
@@ -16379,7 +16444,8 @@ window.__ModuleLoader__.load({
16379
16444
  const inject = [
16380
16445
  "slots",
16381
16446
  "locale",
16382
- "configForms"
16447
+ "configForms",
16448
+ "layout"
16383
16449
  ];
16384
16450
  /**
16385
16451
  * Mount the SSH panel.
@@ -16401,7 +16467,7 @@ window.__ModuleLoader__.load({
16401
16467
  setRuntimeTranslate(ctx.locale.bind(NS));
16402
16468
  } catch {}
16403
16469
  const controller = new PanelController({ panel: { select: (panelId) => {
16404
- ctx.get("layout")?.selectPanel?.(panelId);
16470
+ (ctx.get?.("layout"))?.selectPanel?.(panelId);
16405
16471
  } } });
16406
16472
  const api = new SshApi();
16407
16473
  const settings = bindSettingsReader(ctx, SETTINGS_NS, TERMINAL_FONT_FIELD);
@@ -16418,7 +16484,7 @@ window.__ModuleLoader__.load({
16418
16484
  const disposers = [];
16419
16485
  try {
16420
16486
  disposers.push(registerSshPanel(ctx, controller, api, terminalFont));
16421
- const layoutFace = ctx.get("layout");
16487
+ const layoutFace = ctx.get?.("layout");
16422
16488
  if (layoutFace?.panelInfo !== void 0) {
16423
16489
  const sync = () => {
16424
16490
  const active = layoutFace.panelInfo.getSnapshot().activePanelId;