@linxin666/dsh-remote-web-ui 0.1.19 → 0.1.20

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 (86) hide show
  1. package/lib/client.js +20 -3
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +119 -78
  4. package/lib/mobile.js +18 -5
  5. package/lib/mobile.js.map +1 -1
  6. package/lib/types/client/FooterRemoteEntry.js +1 -1
  7. package/lib/types/client/PluginSettingsCard.d.ts +1 -1
  8. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  9. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  10. package/lib/types/client/RemoteEntry.js +41 -11
  11. package/lib/types/client/RemotePanel.d.ts +1 -0
  12. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  13. package/lib/types/client/RemotePanel.js +2 -2
  14. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  15. package/lib/types/client/RemoteSettingsCard.js +2 -2
  16. package/lib/types/client/UpdateEntry.js +1 -1
  17. package/lib/types/client/UpdatePanel.js +1 -1
  18. package/lib/types/client/deep-link.js +1 -1
  19. package/lib/types/client/index.js +7 -7
  20. package/lib/types/client/settings-form.d.ts.map +1 -1
  21. package/lib/types/client/update-api.d.ts.map +1 -1
  22. package/lib/types/gate.js +1 -1
  23. package/lib/types/http.d.ts +14 -0
  24. package/lib/types/http.d.ts.map +1 -0
  25. package/lib/types/http.js +27 -0
  26. package/lib/types/index.d.ts +3 -1
  27. package/lib/types/index.d.ts.map +1 -1
  28. package/lib/types/index.js +12 -10
  29. package/lib/types/invariant.d.ts.map +1 -1
  30. package/lib/types/mobile/api.js +1 -1
  31. package/lib/types/mobile/index.js +3 -3
  32. package/lib/types/mobile/markdown.d.ts +6 -4
  33. package/lib/types/mobile/markdown.d.ts.map +1 -1
  34. package/lib/types/mobile/markdown.js +8 -4
  35. package/lib/types/mobile/markdown.test.js +5 -1
  36. package/lib/types/mobile/messages.d.ts.map +1 -1
  37. package/lib/types/mobile/messages.js +14 -1
  38. package/lib/types/mobile/messages.test.js +1 -1
  39. package/lib/types/mobile/mobile-theme.test.js +2 -2
  40. package/lib/types/mobile/mux.d.ts.map +1 -1
  41. package/lib/types/mobile/mux.js +1 -1
  42. package/lib/types/mobile/mux.test.js +1 -1
  43. package/lib/types/mobile/rpc.d.ts.map +1 -1
  44. package/lib/types/mobile/theme-toggle.js +1 -1
  45. package/lib/types/mobile/views/App.d.ts +0 -11
  46. package/lib/types/mobile/views/App.d.ts.map +1 -1
  47. package/lib/types/mobile/views/App.js +6 -6
  48. package/lib/types/mobile/views/ChatView.js +6 -6
  49. package/lib/types/mobile/views/ChatView.test.js +3 -3
  50. package/lib/types/mobile/views/SessionListView.js +3 -3
  51. package/lib/types/mobile/views/SessionListView.test.js +2 -2
  52. package/lib/types/mobile/views/WorkspaceView.js +3 -3
  53. package/lib/types/mobile/views/WorkspaceView.test.js +3 -3
  54. package/lib/types/mobile-api.d.ts +1 -1
  55. package/lib/types/mobile-api.d.ts.map +1 -1
  56. package/lib/types/mobile-api.js +11 -18
  57. package/lib/types/mobile-routes.d.ts.map +1 -1
  58. package/lib/types/mobile-routes.js +17 -11
  59. package/lib/types/mount-once.d.ts +25 -0
  60. package/lib/types/mount-once.d.ts.map +1 -0
  61. package/lib/types/mount-once.js +42 -0
  62. package/lib/types/pairing.d.ts.map +1 -1
  63. package/lib/types/routes.d.ts +6 -6
  64. package/lib/types/routes.d.ts.map +1 -1
  65. package/lib/types/routes.js +14 -19
  66. package/lib/types/tunnel.d.ts.map +1 -1
  67. package/lib/types/update-routes.d.ts +2 -2
  68. package/lib/types/update-routes.d.ts.map +1 -1
  69. package/lib/types/update-routes.js +1 -6
  70. package/lib/types/update.d.ts.map +1 -1
  71. package/lib/types/update.js +6 -3
  72. package/package.json +1 -1
  73. package/src/client/RemoteEntry.tsx +38 -6
  74. package/src/client/RemotePanel.tsx +1 -1
  75. package/src/http.ts +30 -0
  76. package/src/index.ts +4 -1
  77. package/src/mobile/markdown.test.ts +5 -0
  78. package/src/mobile/markdown.ts +7 -4
  79. package/src/mobile/messages.ts +14 -1
  80. package/src/mobile/views/App.tsx +0 -12
  81. package/src/mobile-api.ts +9 -17
  82. package/src/mobile-routes.ts +16 -10
  83. package/src/mount-once.ts +48 -0
  84. package/src/routes.ts +11 -17
  85. package/src/update-routes.ts +1 -7
  86. package/src/update.ts +6 -3
@@ -1 +1 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/App.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAA2B,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAazF,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CACnD,GAAG,WAAW,CAad;AAED,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;GAGG;AACH,wBAAgB,GAAG,gCA4DlB;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKhD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIjE;AAED,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,4CAEtF;AAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/App.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAA2B,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAazF,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CACnD,GAAG,WAAW,CAad;AAED,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;GAGG;AACH,wBAAgB,GAAG,gCA4DlB;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKhD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIjE;AAED,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,4CAEtF;AAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,CAAA"}
@@ -6,12 +6,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  * affordance, and every piece of data is fetched on demand.
7
7
  */
8
8
  import { useCallback, useEffect, useRef, useState } from 'react';
9
- import { history as fetchHistory, listSessions, listWorkspaces, prompt } from "../api.js";
10
- import { MuxClient } from "../mux.js";
11
- import { RpcCallError, RpcTransportError } from "../rpc.js";
12
- import { ChatView } from "./ChatView.js";
13
- import { SessionListView } from "./SessionListView.js";
14
- import { WorkspaceView as WorkspaceRoster } from "./WorkspaceView.js";
9
+ import { history as fetchHistory, listSessions, listWorkspaces, prompt } from '../api.js';
10
+ import { MuxClient } from '../mux.js';
11
+ import { RpcCallError, RpcTransportError } from '../rpc.js';
12
+ import { ChatView } from './ChatView.js';
13
+ import { SessionListView } from './SessionListView.js';
14
+ import { WorkspaceView as WorkspaceRoster } from './WorkspaceView.js';
15
15
  /** Read the optional workspace target carried from the pairing QR flow. */
16
16
  export function mobileWorkspaceTarget(search) {
17
17
  const value = new URLSearchParams(search).get('workspace');
@@ -12,12 +12,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  * permission pickers, both as bottom sheets.
13
13
  */
14
14
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
- import { loadHistory, prompt } from "./App.js";
16
- import { errorText, formatTime, staleHostHint } from "./App.js";
17
- import { fetchMobilePreferences, models, selectModel, sendCommand } from "../api.js";
18
- import { foldEvents } from "../messages.js";
19
- import { renderMarkdown } from "../markdown.js";
20
- import { ThemeToggle } from "../theme-toggle.js";
15
+ import { loadHistory, prompt } from './App.js';
16
+ import { errorText, formatTime, staleHostHint } from './App.js';
17
+ import { fetchMobilePreferences, models, selectModel, sendCommand } from '../api.js';
18
+ import { foldEvents } from '../messages.js';
19
+ import { renderMarkdown } from '../markdown.js';
20
+ import { ThemeToggle } from '../theme-toggle.js';
21
21
  /**
22
22
  * Hard cap on live events buffered while the initial history tail page is in
23
23
  * flight. Beyond this the oldest buffered event is dropped and a follow-up
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /** ChatView: collapsible message folds, toolbar chips, and the bottom sheets. */
4
4
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
  import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
6
- import { ChatView, MAX_TAIL_BUFFER_EVENTS } from "./ChatView.js";
6
+ import { ChatView, MAX_TAIL_BUFFER_EVENTS } from './ChatView.js';
7
7
  // The api module is fully mocked; App.tsx's history wrapper is overridden to
8
8
  // feed fixed history pages, its pure helpers (errorText / formatTime) stay real.
9
9
  vi.mock('../api.ts', () => ({
@@ -20,8 +20,8 @@ vi.mock('./App.tsx', async (importOriginal) => {
20
20
  prompt: vi.fn(async () => { }),
21
21
  };
22
22
  });
23
- import { fetchMobilePreferences, models, selectModel, sendCommand } from "../api.js";
24
- import { loadHistory, prompt } from "./App.js";
23
+ import { fetchMobilePreferences, models, selectModel, sendCommand } from '../api.js';
24
+ import { loadHistory, prompt } from './App.js';
25
25
  const session = {
26
26
  sessionId: 's-1',
27
27
  title: '测试会话',
@@ -12,9 +12,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  * the new chat — the same "new session opens" flow as the desktop UI.
13
13
  */
14
14
  import { useCallback, useEffect, useRef, useState } from 'react';
15
- import { createSession, listSessions, listWorkspaces } from "../api.js";
16
- import { errorText, formatTime, staleHostHint, toSessionView } from "./App.js";
17
- import { ThemeToggle } from "../theme-toggle.js";
15
+ import { createSession, listSessions, listWorkspaces } from '../api.js';
16
+ import { errorText, formatTime, staleHostHint, toSessionView } from './App.js';
17
+ import { ThemeToggle } from '../theme-toggle.js';
18
18
  /** Rows that belong to the opened workspace (its owned session id set). */
19
19
  function ownedItems(page, workspace) {
20
20
  const owned = new Set(workspace.sessionIds);
@@ -3,14 +3,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /** SessionListView: owned-row filtering, incremental pages, session creation. */
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
  import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
6
- import { SessionListView } from "./SessionListView.js";
6
+ import { SessionListView } from './SessionListView.js';
7
7
  // The api module is fully mocked; the view's App.tsx helpers stay real.
8
8
  vi.mock('../api.ts', () => ({
9
9
  listSessions: vi.fn(),
10
10
  listWorkspaces: vi.fn(),
11
11
  createSession: vi.fn(),
12
12
  }));
13
- import { createSession, listSessions, listWorkspaces } from "../api.js";
13
+ import { createSession, listSessions, listWorkspaces } from '../api.js';
14
14
  const listSessionsMock = vi.mocked(listSessions);
15
15
  const listWorkspacesMock = vi.mocked(listWorkspaces);
16
16
  const createSessionMock = vi.mocked(createSession);
@@ -6,9 +6,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  * until a workspace is opened).
7
7
  */
8
8
  import { useEffect, useState } from 'react';
9
- import { listWorkspaces } from "../api.js";
10
- import { errorText } from "./App.js";
11
- import { ThemeToggle } from "../theme-toggle.js";
9
+ import { listWorkspaces } from '../api.js';
10
+ import { errorText } from './App.js';
11
+ import { ThemeToggle } from '../theme-toggle.js';
12
12
  /**
13
13
  * Render the workspace roster.
14
14
  * @param props - the pick action.
@@ -3,12 +3,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /** Mobile workspace landing: roster rendering and QR deep-link selection. */
4
4
  import { afterEach, describe, expect, it, vi } from 'vitest';
5
5
  import { cleanup, render, screen, waitFor } from '@testing-library/react';
6
- import { mobileWorkspaceTarget } from "./App.js";
7
- import { WorkspaceView } from "./WorkspaceView.js";
6
+ import { mobileWorkspaceTarget } from './App.js';
7
+ import { WorkspaceView } from './WorkspaceView.js';
8
8
  vi.mock('../api.ts', () => ({
9
9
  listWorkspaces: vi.fn(),
10
10
  }));
11
- import { listWorkspaces } from "../api.js";
11
+ import { listWorkspaces } from '../api.js';
12
12
  const listWorkspacesMock = vi.mocked(listWorkspaces);
13
13
  const workspaces = [
14
14
  {
@@ -34,7 +34,7 @@ export interface MobileApiDeps {
34
34
  }
35
35
  /** Mobile API route paths. */
36
36
  export declare const MOBILE_API_PATHS: {
37
- readonly events: "/m/api/events.mux";
37
+ readonly events: '/m/api/events.mux';
38
38
  };
39
39
  /**
40
40
  * Build the mobile data-channel routes.
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-api.d.ts","sourceRoot":"","sources":["../../src/mobile-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAA;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAgDlD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,CAAA;IAClB,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,OAAO,CAAA;IAChC,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB;;CAEnB,CAAA;AAOV;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CA6InE"}
1
+ {"version":3,"file":"mobile-api.d.ts","sourceRoot":"","sources":["../../src/mobile-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAA;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAiDlD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,CAAA;IAClB,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,OAAO,CAAA;IAChC,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB;aAC3B,MAAM,EAAE,mBAAmB;CACnB,CAAA;AAOV;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CA4InE"}
@@ -19,7 +19,8 @@
19
19
  * unary channel), gated identically.
20
20
  */
21
21
  import { RpcId } from '@deepseek-ai/dsh-host-apiproxy/api/rpc';
22
- import { readCookie } from "./gate.js";
22
+ import { readBoundedJson, writeJson } from './http.js';
23
+ import { readCookie } from './gate.js';
23
24
  /** Methods the phone surface may call. Everything else is refused. */
24
25
  const MOBILE_ALLOWLIST = new Set([
25
26
  'workspace.list',
@@ -95,10 +96,6 @@ export function makeMobileApiRoutes(deps) {
95
96
  const gateOk = (req) => {
96
97
  return touchDeviceFor(req);
97
98
  };
98
- const writeJson = (res, status, body) => {
99
- res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
100
- res.end(JSON.stringify(body));
101
- };
102
99
  const handleMethod = async (req, res) => {
103
100
  if (req.method !== 'POST') {
104
101
  res.writeHead(405);
@@ -143,7 +140,12 @@ export function makeMobileApiRoutes(deps) {
143
140
  return;
144
141
  }
145
142
  try {
146
- const response = await dispatch(apiProxy, method, parsed?.payload, rpcId);
143
+ // Cancel the host-side work when the phone goes away mid-call (the
144
+ // response stream closing before we answer means nobody is listening).
145
+ const abort = new AbortController();
146
+ res.on('close', () => { if (!res.writableEnded)
147
+ abort.abort(); });
148
+ const response = await dispatch(apiProxy, method, parsed?.payload, rpcId, abort.signal);
147
149
  writeJson(res, 200, response);
148
150
  }
149
151
  catch (error) {
@@ -222,19 +224,10 @@ export function makeMobileApiRoutes(deps) {
222
224
  }
223
225
  /** Read a request body as JSON (bounded). */
224
226
  async function readJsonBody(req) {
225
- const chunks = [];
226
- let size = 0;
227
- for await (const chunk of req) {
228
- const buffer = chunk;
229
- size += buffer.length;
230
- if (size > 64 * 1024)
231
- throw new Error('body too large');
232
- chunks.push(buffer);
233
- }
234
- return JSON.parse(Buffer.concat(chunks).toString('utf8'));
227
+ return readBoundedJson(req, 64 * 1024);
235
228
  }
236
229
  /** Dispatch one allowlisted method through the host ApiProxy. */
237
- async function dispatch(apiProxy, method, payload, rpcId) {
230
+ async function dispatch(apiProxy, method, payload, rpcId, signal) {
238
231
  const request = { rpcId: RpcId(rpcId), payload };
239
232
  if (method === 'session.list') {
240
233
  const full = await apiProxy.sessions.list(request);
@@ -285,7 +278,7 @@ async function dispatch(apiProxy, method, payload, rpcId) {
285
278
  if (method === 'session.history')
286
279
  return wrap(await apiProxy.sessions.history(request));
287
280
  if (method === 'session.search')
288
- return wrap(await apiProxy.sessions.search(request, new AbortController().signal));
281
+ return wrap(await apiProxy.sessions.search(request, signal ?? new AbortController().signal));
289
282
  if (method === 'session.prompt')
290
283
  return wrap(await apiProxy.sessions.prompt(request));
291
284
  if (method === 'session.models')
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-routes.d.ts","sourceRoot":"","sources":["../../src/mobile-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAwD/D;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,EAAE,CAmC7C"}
1
+ {"version":3,"file":"mobile-routes.d.ts","sourceRoot":"","sources":["../../src/mobile-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAwD/D;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,EAAE,CAyC7C"}
@@ -67,19 +67,25 @@ export function makeMobileRoutes() {
67
67
  const handlePage = (_req, res) => {
68
68
  writeStatic(res, 200, 'text/html', pageHtml('/m/mobile.js'));
69
69
  };
70
+ // The bundle is immutable for the process lifetime (a rebuild requires a
71
+ // host restart), so the ~456KB body is read from disk once, not per phone.
72
+ let bundleBody;
70
73
  const handleBundle = async (_req, res) => {
71
- const path = mobileBundlePath();
72
- if (!existsSync(path)) {
73
- writeStatic(res, 503, 'text/plain', 'mobile bundle not built: run pnpm --filter @linxin666/dsh-remote-web-ui build');
74
- return;
75
- }
76
- try {
77
- const body = await readFile(path, 'utf8');
78
- writeStatic(res, 200, 'text/javascript', body);
79
- }
80
- catch {
81
- writeStatic(res, 500, 'text/plain', 'failed to read the mobile bundle');
74
+ if (bundleBody === undefined) {
75
+ const path = mobileBundlePath();
76
+ if (!existsSync(path)) {
77
+ writeStatic(res, 503, 'text/plain', 'mobile bundle not built: run pnpm --filter @linxin666/dsh-remote-web-ui build');
78
+ return;
79
+ }
80
+ try {
81
+ bundleBody = await readFile(path, 'utf8');
82
+ }
83
+ catch {
84
+ writeStatic(res, 500, 'text/plain', 'failed to read the mobile bundle');
85
+ return;
86
+ }
82
87
  }
88
+ writeStatic(res, 200, 'text/javascript', bundleBody);
83
89
  };
84
90
  const handleIcon = async (_req, res) => {
85
91
  const path = appleTouchIconPath();
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Host single-instance guard shared by the plugin family. The family bundle
3
+ * (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
4
+ * the loader accepts a standalone install of the same package side by side;
5
+ * without this guard the second instance would still re-register the same
6
+ * webserver routes, tools, settings namespaces, and system-prompt sections
7
+ * and fail the boot. mountOnce makes the second host apply a no-op for the
8
+ * lifetime of the first instance (the browser half is already deduped by
9
+ * package name in the client module host).
10
+ *
11
+ * The registry rides a global symbol so two module instances of the same
12
+ * package (npm copy vs repository link) still share one verdict. cordis
13
+ * `ctx.effect` runs its callback immediately and treats the callback's
14
+ * return value as the fiber disposer, so the unmarker is returned, not run.
15
+ */
16
+ /**
17
+ * Wrap a cordis plugin apply so the package runs at most once per process.
18
+ * The first mount registers normally and unmarks when its fiber disposes;
19
+ * any later mount of the same package name is a no-op.
20
+ * @param packageName - npm package identity shared by every install source.
21
+ * @param fn - the original plugin apply.
22
+ * @returns an apply of the same shape.
23
+ */
24
+ export declare function mountOnce<T extends (...args: any[]) => unknown>(packageName: string, fn: T): T;
25
+ //# sourceMappingURL=mount-once.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-once.d.ts","sourceRoot":"","sources":["../../src/mount-once.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAaH;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAW9F"}
@@ -0,0 +1,42 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/host/mount-once.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
+ /**
3
+ * Host single-instance guard shared by the plugin family. The family bundle
4
+ * (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
5
+ * the loader accepts a standalone install of the same package side by side;
6
+ * without this guard the second instance would still re-register the same
7
+ * webserver routes, tools, settings namespaces, and system-prompt sections
8
+ * and fail the boot. mountOnce makes the second host apply a no-op for the
9
+ * lifetime of the first instance (the browser half is already deduped by
10
+ * package name in the client module host).
11
+ *
12
+ * The registry rides a global symbol so two module instances of the same
13
+ * package (npm copy vs repository link) still share one verdict. cordis
14
+ * `ctx.effect` runs its callback immediately and treats the callback's
15
+ * return value as the fiber disposer, so the unmarker is returned, not run.
16
+ */
17
+ const MOUNTED = Symbol.for('dsh-web-ui.mounted-plugins');
18
+ function mountedSet() {
19
+ const registry = globalThis;
20
+ return (registry[MOUNTED] ??= new Set());
21
+ }
22
+ /**
23
+ * Wrap a cordis plugin apply so the package runs at most once per process.
24
+ * The first mount registers normally and unmarks when its fiber disposes;
25
+ * any later mount of the same package name is a no-op.
26
+ * @param packageName - npm package identity shared by every install source.
27
+ * @param fn - the original plugin apply.
28
+ * @returns an apply of the same shape.
29
+ */
30
+ export function mountOnce(packageName, fn) {
31
+ return ((...args) => {
32
+ const mounted = mountedSet();
33
+ if (mounted.has(packageName))
34
+ return;
35
+ mounted.add(packageName);
36
+ const ctx = args[0];
37
+ ctx?.effect?.(() => () => {
38
+ mounted.delete(packageName);
39
+ });
40
+ return fn(...args);
41
+ });
42
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/pairing.ts"],"names":[],"mappings":"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"}
@@ -37,12 +37,12 @@ export declare function isTrustedApiRequest(request: IncomingMessage, trustedHos
37
37
  export declare function publicHostOf(url: string | undefined): string | undefined;
38
38
  /** Route paths (exact matches under /api). */
39
39
  export declare const PAIR_PATHS: {
40
- readonly issue: "/api/pair/issue";
41
- readonly accept: "/api/pair/accept";
42
- readonly stop: "/api/pair/stop";
43
- readonly heartbeat: "/api/pair/heartbeat";
44
- readonly status: "/api/pair/status";
45
- readonly events: "/api/pair/events";
40
+ readonly issue: '/api/pair/issue';
41
+ readonly accept: '/api/pair/accept';
42
+ readonly stop: '/api/pair/stop';
43
+ readonly heartbeat: '/api/pair/heartbeat';
44
+ readonly status: '/api/pair/status';
45
+ readonly events: '/api/pair/events';
46
46
  };
47
47
  /**
48
48
  * /api/pair request payload contracts. Each POST endpoint validates its body
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,EAAE,CAAC,EAAgB,MAAM,KAAK,CAAA;AACrC,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAGhG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA6BtG;AAKD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOxE;AAKD,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAA;AAEV;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;;iBAG7B,CAAA;AACF,eAAO,MAAM,mBAAmB;;iBAE9B,CAAA;AACF,eAAO,MAAM,uBAAuB,gCAA6B,CAAA;AA4CjE,gDAAgD;AAChD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD;;OAEG;gBACS,OAAO,EAAE,cAAc;IAInC,4DAA4D;IAC5D,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI;IAiBrD,kEAAkE;IAClE,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAYrC,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,OAAO,EAAE,cAAc,CAAA;IACvB,0EAA0E;IAC1E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,QAAQ,EAAE,CA4K3D"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,EAAE,CAAC,EAAgB,MAAM,KAAK,CAAA;AACrC,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAIhG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA6BtG;AAKD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOxE;AAKD,8CAA8C;AAC9C,eAAO,MAAM,UAAU;aACrB,KAAK,EAAE,iBAAiB;aACxB,MAAM,EAAE,kBAAkB;aAC1B,IAAI,EAAE,gBAAgB;aACtB,SAAS,EAAE,qBAAqB;aAChC,MAAM,EAAE,kBAAkB;aAC1B,MAAM,EAAE,kBAAkB;CAClB,CAAA;AAEV;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;;iBAG7B,CAAA;AACF,eAAO,MAAM,mBAAmB;;iBAE9B,CAAA;AACF,eAAO,MAAM,uBAAuB,gCAA6B,CAAA;AA6BjE,gDAAgD;AAChD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD;;OAEG;IACH,YAAY,OAAO,EAAE,cAAc,EAElC;IAED,4DAA4D;IAC5D,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAepD;IAED,kEAAkE;IAClE,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAUpC;IAED,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,OAAO,EAAE,cAAc,CAAA;IACvB,0EAA0E;IAC1E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,QAAQ,EAAE,CAoL3D"}
@@ -8,8 +8,9 @@
8
8
  * on every other /api request.
9
9
  */
10
10
  import { z } from 'zod';
11
- import { UnknownLanAddressError } from "./pairing.js";
12
- import { isLoopbackClient, readCookie } from "./gate.js";
11
+ import { UnknownLanAddressError } from './pairing.js';
12
+ import { isLoopbackClient, readCookie } from './gate.js';
13
+ import { readBoundedJson, writeJson } from './http.js';
13
14
  /**
14
15
  * Browser-trust fence for the /api/pair routes, mirroring the connection
15
16
  * package's internal fence semantics (Host/Origin based, DNS-rebinding and
@@ -117,25 +118,10 @@ function parsePairPayload(schema, body) {
117
118
  const result = schema.safeParse(body ?? {});
118
119
  return result.success ? result.data : undefined;
119
120
  }
120
- /** One JSON response. */
121
- function writeJson(res, status, body) {
122
- const payload = JSON.stringify(body);
123
- res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'referrer-policy': 'no-referrer' });
124
- res.end(payload);
125
- }
126
- /** Read a request body up to MAX_BODY_BYTES and parse it as JSON. */
121
+ /** Read a request body up to MAX_BODY_BYTES and parse it as JSON (undefined on failure). */
127
122
  async function readJsonBody(req) {
128
- const chunks = [];
129
- let size = 0;
130
- for await (const chunk of req) {
131
- const buffer = chunk;
132
- size += buffer.length;
133
- if (size > MAX_BODY_BYTES)
134
- return undefined;
135
- chunks.push(buffer);
136
- }
137
123
  try {
138
- const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
124
+ const parsed = await readBoundedJson(req, MAX_BODY_BYTES);
139
125
  return typeof parsed === 'object' && parsed !== null ? parsed : undefined;
140
126
  }
141
127
  catch {
@@ -216,6 +202,15 @@ export function makeRoutes(deps) {
216
202
  const rateLimitAccept = (req) => {
217
203
  const ip = req.socket?.remoteAddress ?? 'unknown';
218
204
  const nowMs = Date.now();
205
+ // The map lives as long as the plugin: prune expired windows once the
206
+ // table grows past a modest size so distinct source IPs (LAN clients,
207
+ // brute-force scans) cannot accumulate forever.
208
+ if (acceptAttempts.size > 256) {
209
+ for (const [key, attempt] of acceptAttempts) {
210
+ if (nowMs - attempt.windowStart > ACCEPT_WINDOW_MS)
211
+ acceptAttempts.delete(key);
212
+ }
213
+ }
219
214
  const entry = acceptAttempts.get(ip);
220
215
  if (entry === undefined || nowMs - entry.windowStart > ACCEPT_WINDOW_MS) {
221
216
  acceptAttempts.set(ip, { count: 1, windowStart: nowMs });
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEvE,+BAA+B;AAC/B,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,WAAW,CAAA;IAClB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;IAC9D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;IAC/D,IAAI,IAAI,OAAO,CAAA;CAChB;AAED,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,YAAY,CAAA;IAC7C,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,wCAAwC;IACxC,KAAK,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;CAC5F;AAkBD;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqF;IAE3G,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmC;IAChE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwC;IAEvE;;OAEG;gBACS,OAAO,GAAE,oBAAyB;IAS9C,gCAAgC;IAChC,IAAI,IAAI,IAAI,UAAU,CAMrB;IAED;;;;;OAKG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAS9B,uDAAuD;IACvD,IAAI,IAAI,IAAI;IAOZ,wDAAwD;IACxD,OAAO,IAAI,IAAI;IAIf,4EAA4E;IAC5E,KAAK,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAKlD,uCAAuC;IACvC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAKzD,OAAO,CAAC,OAAO;IAoCf,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,IAAI;IAqBZ,iFAAiF;IACjF,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,QAAQ;CAWjB"}
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"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"}
@@ -9,8 +9,8 @@ import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
9
9
  import type { UpdateRunResult, UpdateStatus } from './update.ts';
10
10
  /** Route paths (exact matches under /api). */
11
11
  export declare const UPDATE_PATHS: {
12
- readonly status: "/api/update/status";
13
- readonly run: "/api/update/run";
12
+ readonly status: '/api/update/status';
13
+ readonly run: '/api/update/run';
14
14
  };
15
15
  /** Route-family dependencies (test seam). */
16
16
  export interface UpdateRoutesDeps {
@@ -1 +1 @@
1
- {"version":3,"file":"update-routes.d.ts","sourceRoot":"","sources":["../../src/update-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAEhE,8CAA8C;AAC9C,eAAO,MAAM,YAAY;;;CAGf,CAAA;AAEV,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,KAAK,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAA;IACxC,yDAAyD;IACzD,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC,CAAA;IAC9B,sDAAsD;IACtD,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,CAAA;CAChC;AASD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,QAAQ,EAAE,CA+BnE"}
1
+ {"version":3,"file":"update-routes.d.ts","sourceRoot":"","sources":["../../src/update-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAGhE,8CAA8C;AAC9C,eAAO,MAAM,YAAY;aACvB,MAAM,EAAE,oBAAoB;aAC5B,GAAG,EAAE,iBAAiB;CACd,CAAA;AAEV,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,KAAK,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAA;IACxC,yDAAyD;IACzD,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC,CAAA;IAC9B,sDAAsD;IACtD,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,QAAQ,EAAE,CA+BnE"}
@@ -4,17 +4,12 @@
4
4
  * pnpm install inside the owning profile, so it must never be reachable
5
5
  * from a LAN/phone origin.
6
6
  */
7
+ import { writeJson } from './http.js';
7
8
  /** Route paths (exact matches under /api). */
8
9
  export const UPDATE_PATHS = {
9
10
  status: '/api/update/status',
10
11
  run: '/api/update/run',
11
12
  };
12
- /** One JSON response. */
13
- function writeJson(res, status, body) {
14
- const payload = JSON.stringify(body);
15
- res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'referrer-policy': 'no-referrer' });
16
- res.end(payload);
17
- }
18
13
  /**
19
14
  * Build the /api/update route family.
20
15
  * @param deps - fence + check/run seams.
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
@@ -268,10 +268,13 @@ export async function checkUpdates(deps) {
268
268
  return { mode: 'link', packages: [], outdated: false };
269
269
  }
270
270
  const names = [anchor, ...familyChildren(manifest)];
271
+ // The registry probes are independent: run them together instead of
272
+ // serializing up to N x 10s of registry latency behind one status call.
273
+ const latestList = await Promise.all(names.map(name => deps.fetchLatest(name)));
271
274
  const packages = [];
272
275
  let probeFailures = 0;
273
- for (const name of names) {
274
- const latest = await deps.fetchLatest(name);
276
+ names.forEach((name, index) => {
277
+ const latest = latestList[index];
275
278
  if (latest === undefined)
276
279
  probeFailures++;
277
280
  const current = readInstalledVersion(deps.resolve, name);
@@ -281,7 +284,7 @@ export async function checkUpdates(deps) {
281
284
  latest,
282
285
  outdated: latest !== undefined && latest !== current && compareVersions(latest, current) > 0,
283
286
  });
284
- }
287
+ });
285
288
  // Registry unreachable: every probe failed — report the outage distinctly
286
289
  // instead of a misleading "all up to date" (the panel needs the reason).
287
290
  const error = probeFailures === names.length && names.length > 0 ? 'registry-unreachable' : undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-remote-web-ui",
3
3
  "description": "Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions",
4
- "version": "0.1.19",
4
+ "version": "0.1.20",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -20,8 +20,18 @@ import css from './remote.module.css'
20
20
  /** Entry props: the sidebar column state + the standard locale seat. */
21
21
  export type RemoteEntryProps = PropsRuntime<'sidebar.remote'> & PropsLocale<'remote'>
22
22
 
23
- /** Apply one status frame onto the current ready state. */
23
+ /**
24
+ * Apply one status frame onto the current state: the ready state mirrors
25
+ * the full phase/device picture, while the lan-required banner only keeps
26
+ * the auto-tunnel frame (the signal for the running re-issue).
27
+ */
24
28
  function mergeFrame(state: PanelState, frame: PairStateFrame): PanelState {
29
+ if (state.kind === 'lan-required') {
30
+ return {
31
+ ...state,
32
+ ...(frame.tunnel !== undefined ? { tunnel: frame.tunnel } : {}),
33
+ }
34
+ }
25
35
  if (state.kind !== 'ready') return state
26
36
  return {
27
37
  ...state,
@@ -40,6 +50,11 @@ function mergeFrame(state: PanelState, frame: PairStateFrame): PanelState {
40
50
  export function RemoteEntry({ wide, useWorkspaces, t }: RemoteEntryProps) {
41
51
  const [open, setOpen] = useState(false)
42
52
  const [state, setState] = useState<PanelState>({ kind: 'lan-required' })
53
+ // Latest-state mirror for the EventSource callback: transition detection
54
+ // must live outside setState updaters (updaters may run twice and must be
55
+ // pure), so mint decisions read this ref instead.
56
+ const stateRef = useRef(state)
57
+ useEffect(() => { stateRef.current = state }, [state])
43
58
  const [copied, setCopied] = useState(false)
44
59
  const eventSource = useRef<EventSource | undefined>(undefined)
45
60
 
@@ -93,17 +108,34 @@ export function RemoteEntry({ wide, useWorkspaces, t }: RemoteEntryProps) {
93
108
  const next = await mint()
94
109
  setState(next)
95
110
  // Live status: the desktop panel mirrors the pairing service state. The
96
- // stream only makes sense in the ready state — on a failure banner the
97
- // events endpoint is unreachable too (loopback fence), so opening it
98
- // would just start a doomed reconnect loop.
99
- if (next.kind !== 'ready') return
111
+ // stream makes sense in the ready state and on the lan-required banner —
112
+ // there the auto-tunnel may still be starting, and following its frames
113
+ // lets the panel re-issue once it runs. The loopback-required and
114
+ // unreachable origins are fenced out of the events endpoint, so opening
115
+ // it there would just start a doomed reconnect loop.
116
+ if (next.kind !== 'ready' && next.kind !== 'lan-required') return
100
117
  const source = new EventSource('/api/pair/events')
101
118
  eventSource.current = source
102
119
  source.onmessage = (event) => {
103
120
  try {
104
121
  const frame = JSON.parse(event.data as string) as PairStateFrame
105
122
  if (frame.type !== 'state') return
106
- setState(previous => mergeFrame(previous, frame))
123
+ // The auto-tunnel crossed into running while the panel sat on the
124
+ // lan-required banner: re-issue so the server hands back a ready QR
125
+ // built on the public base (only on the transition into running, to
126
+ // avoid mint storms). Detected on the ref, outside the updater: an
127
+ // updater may be invoked twice and must stay pure, so mint() cannot
128
+ // run inside it.
129
+ const previous = stateRef.current
130
+ if (
131
+ previous.kind === 'lan-required'
132
+ && frame.tunnel?.state === 'running'
133
+ && previous.tunnel?.state !== 'running'
134
+ ) {
135
+ void mint().then(setState)
136
+ return
137
+ }
138
+ setState(current => mergeFrame(current, frame))
107
139
  } catch {
108
140
  // Malformed frames are dropped; the snapshot on open is authoritative.
109
141
  }