dsh-zen-remote 2.0.3 → 2.2.0

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 (114) hide show
  1. package/AGENTS.md +15 -1
  2. package/README.md +11 -5
  3. package/README.zh-CN.md +11 -5
  4. package/docs/adr/0001-plugin-bridge-forwards-plugin-routes.md +15 -0
  5. package/docs/adr/0002-relay-emulates-websocket.md +8 -0
  6. package/docs/interface.md +3 -1
  7. package/docs/plugin-bridge.md +140 -0
  8. package/docs/remote-access.en.md +57 -0
  9. package/docs/remote-access.md +57 -0
  10. package/lib/adapters/dsh-better-sidebar/index.js +144 -0
  11. package/lib/adapters/dsh-better-sidebar/index.js.map +1 -0
  12. package/lib/adapters/dsh-better-sidebar/methods.js +198 -0
  13. package/lib/adapters/dsh-better-sidebar/methods.js.map +1 -0
  14. package/lib/adapters/index.js +17 -0
  15. package/lib/adapters/index.js.map +1 -0
  16. package/lib/adapters/types.js +17 -0
  17. package/lib/adapters/types.js.map +1 -0
  18. package/lib/admin-routes.js +51 -0
  19. package/lib/admin-routes.js.map +1 -1
  20. package/lib/client-routes.js +18 -1
  21. package/lib/client-routes.js.map +1 -1
  22. package/lib/client.js +449 -45
  23. package/lib/config.js +27 -0
  24. package/lib/config.js.map +1 -1
  25. package/lib/http.js +10 -0
  26. package/lib/http.js.map +1 -1
  27. package/lib/index.js +246 -0
  28. package/lib/index.js.map +1 -1
  29. package/lib/plugin-adapter-core.js +385 -0
  30. package/lib/plugin-adapter-core.js.map +1 -0
  31. package/lib/plugin-bridge.js +318 -0
  32. package/lib/plugin-bridge.js.map +1 -0
  33. package/lib/plugin-loopback.js +207 -0
  34. package/lib/plugin-loopback.js.map +1 -0
  35. package/lib/plugin-route-intercept.js +1103 -0
  36. package/lib/plugin-route-intercept.js.map +1 -0
  37. package/lib/plugin-tunnel.js +416 -0
  38. package/lib/plugin-tunnel.js.map +1 -0
  39. package/lib/relay-client.js +319 -2
  40. package/lib/relay-client.js.map +1 -1
  41. package/lib/relay-server.js +633 -0
  42. package/lib/relay-server.js.map +1 -1
  43. package/lib/types/adapters/dsh-better-sidebar/index.d.ts +27 -0
  44. package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -0
  45. package/lib/types/adapters/dsh-better-sidebar/methods.d.ts +51 -0
  46. package/lib/types/adapters/dsh-better-sidebar/methods.d.ts.map +1 -0
  47. package/lib/types/adapters/index.d.ts +17 -0
  48. package/lib/types/adapters/index.d.ts.map +1 -0
  49. package/lib/types/adapters/types.d.ts +42 -0
  50. package/lib/types/adapters/types.d.ts.map +1 -0
  51. package/lib/types/admin-routes.d.ts +16 -0
  52. package/lib/types/admin-routes.d.ts.map +1 -1
  53. package/lib/types/client/effects/remote-session.d.ts +0 -22
  54. package/lib/types/client/effects/remote-session.d.ts.map +1 -1
  55. package/lib/types/client/locales.d.ts +16 -0
  56. package/lib/types/client/locales.d.ts.map +1 -1
  57. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -1
  58. package/lib/types/client/styles/remote-session.css.d.ts +1 -1
  59. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -1
  60. package/lib/types/client-data/plugin-bridge.d.ts +79 -0
  61. package/lib/types/client-data/plugin-bridge.d.ts.map +1 -0
  62. package/lib/types/client-data/remote-status.d.ts +20 -0
  63. package/lib/types/client-data/remote-status.d.ts.map +1 -1
  64. package/lib/types/client-data/settings-form.d.ts +37 -1
  65. package/lib/types/client-data/settings-form.d.ts.map +1 -1
  66. package/lib/types/client-routes.d.ts +13 -0
  67. package/lib/types/client-routes.d.ts.map +1 -1
  68. package/lib/types/config.d.ts +8 -0
  69. package/lib/types/config.d.ts.map +1 -1
  70. package/lib/types/http.d.ts +7 -0
  71. package/lib/types/http.d.ts.map +1 -1
  72. package/lib/types/index.d.ts +4 -0
  73. package/lib/types/index.d.ts.map +1 -1
  74. package/lib/types/plugin-adapter-core.d.ts +263 -0
  75. package/lib/types/plugin-adapter-core.d.ts.map +1 -0
  76. package/lib/types/plugin-bridge.d.ts +187 -0
  77. package/lib/types/plugin-bridge.d.ts.map +1 -0
  78. package/lib/types/plugin-loopback.d.ts +104 -0
  79. package/lib/types/plugin-loopback.d.ts.map +1 -0
  80. package/lib/types/plugin-route-intercept.d.ts +114 -0
  81. package/lib/types/plugin-route-intercept.d.ts.map +1 -0
  82. package/lib/types/plugin-tunnel.d.ts +96 -0
  83. package/lib/types/plugin-tunnel.d.ts.map +1 -0
  84. package/lib/types/relay-client.d.ts +76 -0
  85. package/lib/types/relay-client.d.ts.map +1 -1
  86. package/lib/types/relay-server.d.ts +32 -1
  87. package/lib/types/relay-server.d.ts.map +1 -1
  88. package/llms.txt +1 -0
  89. package/package.json +2 -2
  90. package/scripts/check-plugin-bridge-ui.mjs +168 -0
  91. package/scripts/check-settings-form.mjs +3 -1
  92. package/src/adapters/dsh-better-sidebar/index.ts +144 -0
  93. package/src/adapters/dsh-better-sidebar/methods.ts +215 -0
  94. package/src/adapters/index.ts +19 -0
  95. package/src/adapters/types.ts +44 -0
  96. package/src/admin-routes.ts +64 -0
  97. package/src/client/effects/remote-session.ts +82 -10
  98. package/src/client/locales.ts +36 -0
  99. package/src/client/settings/SettingsSection.tsx +107 -3
  100. package/src/client/styles/remote-session.css.ts +70 -31
  101. package/src/client-data/plugin-bridge.ts +150 -0
  102. package/src/client-data/remote-status.ts +30 -0
  103. package/src/client-data/settings-form.ts +78 -0
  104. package/src/client-routes.ts +29 -1
  105. package/src/config.ts +32 -0
  106. package/src/http.ts +12 -0
  107. package/src/index.ts +252 -0
  108. package/src/plugin-adapter-core.ts +530 -0
  109. package/src/plugin-bridge.ts +435 -0
  110. package/src/plugin-loopback.ts +299 -0
  111. package/src/plugin-route-intercept.ts +1193 -0
  112. package/src/plugin-tunnel.ts +470 -0
  113. package/src/relay-client.ts +394 -2
  114. package/src/relay-server.ts +656 -1
@@ -0,0 +1,318 @@
1
+ /**
2
+ * The plugin bridge's registration and comparison half (PB-1).
3
+ *
4
+ * Other DSH plugins can hand zen-remote a "bridge declaration": the list of
5
+ * THEIR OWN HTTP routes that may be forwarded to the main server inside a
6
+ * remote session (docs/adr/0001). A plugin counts as "bridged" only when
7
+ * BOTH ends agree on the plugin name, the exact version AND the declaration
8
+ * digest — everything here is the pure logic behind that agreement: the
9
+ * declaration shape and its validation ({@link normalizeDeclaration}), the
10
+ * per-process registry backing the `zenRemote` service
11
+ * ({@link createBridgeRegistry}), and the local-vs-server comparison that a
12
+ * later task feeds handshake data into ({@link compareBridges}).
13
+ *
14
+ * Pure logic by design: no DSH imports, no I/O, no clocks — the digest is
15
+ * the only place node:crypto appears, which is fine because this module only
16
+ * ever runs in the backend process. Nothing here talks to the relay or the
17
+ * network; the handshake side and the request rewrite side are later tasks
18
+ * that consume a {@link BridgeRegistry} through src/index.ts.
19
+ */
20
+ import { createHash } from 'node:crypto';
21
+ /** The HTTP methods a bridged route may forward (PB-9). 2.1 accepted GET
22
+ * only; the write methods arrived with the `methods` field. */
23
+ export const BRIDGE_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];
24
+ /** The body cap a route may declare (`maxBody`), in bytes: 64 MiB. */
25
+ export const MAX_ROUTE_BODY_BYTES = 64 * 1024 * 1024;
26
+ /** The body cap a route gets when it declares none: 1 MiB. */
27
+ export const DEFAULT_ROUTE_BODY_BYTES = 1024 * 1024;
28
+ /** The effective upgrade mark of one normalized route. */
29
+ export function routeUpgradesOf(route) {
30
+ return route.upgrade === true;
31
+ }
32
+ /** The effective forwardable methods of one normalized route. */
33
+ export function routeMethodsOf(route) {
34
+ return route.methods ?? ['GET'];
35
+ }
36
+ /** The effective body cap of one normalized route, in bytes. */
37
+ export function routeMaxBodyOf(route) {
38
+ return route.maxBody ?? DEFAULT_ROUTE_BODY_BYTES;
39
+ }
40
+ /** Longest legal `plugin` — the npm package name cap. */
41
+ const MAX_PLUGIN_LENGTH = 214;
42
+ /** Most routes one declaration may carry. */
43
+ const MAX_ROUTES = 64;
44
+ /** npm package name shape: lowercase, URL-safe run of `[a-z0-9._-]` that
45
+ * starts with a letter or digit, optionally `@scope/name` with the same body
46
+ * rules on both halves. Not the full official validator (reserved names,
47
+ * edge punctuation) — enough that a typo'd string cannot masquerade as a
48
+ * package name and that the two ends normalize identically. */
49
+ const PLUGIN_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
50
+ /** Query-parameter name carrying the session id. */
51
+ const SESSION_PARAM_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,63}$/u;
52
+ function isPlainObject(value) {
53
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
54
+ }
55
+ /** Every rejection says WHICH field failed and why. */
56
+ function fail(field, detail) {
57
+ throw new Error(`invalid bridge declaration, ${field}: ${detail}`);
58
+ }
59
+ /**
60
+ * Validate one declaration and fold it into its comparable form.
61
+ *
62
+ * Route paths must be plain absolute paths (start with `/`, no `?`, `#`,
63
+ * `..` or whitespace) and must stay off the two prefixes zen-remote does not
64
+ * own: `/_dsh/` (the plugin's own surface) and `/api` (the host's shared
65
+ * fetch channel) — bridging either would let a third plugin put words in the
66
+ * host's mouth. The `/api` check is the literal string prefix, so a plugin
67
+ * route like `/apifoo` is refused too; that overreach is deliberate, the
68
+ * rule must stay trivially predictable on both ends.
69
+ *
70
+ * The digest is sha256 over a canonical JSON of `{plugin, version, routes}`
71
+ * with a fixed key order, hex, first 16 characters — route ORDER is not part
72
+ * of it (two ends listing the same routes in different orders still match),
73
+ * any field VALUE is.
74
+ * @throws with a field-naming message on any illegal shape.
75
+ */
76
+ export function normalizeDeclaration(input) {
77
+ if (!isPlainObject(input))
78
+ fail('declaration', 'expected an object');
79
+ for (const key of Object.keys(input)) {
80
+ if (key !== 'plugin' && key !== 'version' && key !== 'routes')
81
+ fail(key, 'unknown field (typo guard)');
82
+ }
83
+ const { plugin, version, routes } = input;
84
+ if (typeof plugin !== 'string' || plugin === '')
85
+ fail('plugin', 'must be a non-empty string');
86
+ if (plugin.length > MAX_PLUGIN_LENGTH)
87
+ fail('plugin', `longer than ${MAX_PLUGIN_LENGTH} characters`);
88
+ if (!PLUGIN_PATTERN.test(plugin))
89
+ fail('plugin', `"${plugin}" is not an npm package name`);
90
+ if (typeof version !== 'string' || version.trim() === '')
91
+ fail('version', 'must be a non-empty string');
92
+ if (!Array.isArray(routes) || routes.length === 0)
93
+ fail('routes', 'must be a non-empty array');
94
+ if (routes.length > MAX_ROUTES)
95
+ fail('routes', `${routes.length} routes exceeds the ${MAX_ROUTES} cap`);
96
+ const seen = new Set();
97
+ const normalizedRoutes = routes.map((route, index) => {
98
+ if (!isPlainObject(route))
99
+ fail(`routes[${index}]`, 'expected an object');
100
+ for (const key of Object.keys(route)) {
101
+ if (key !== 'path' && key !== 'scope' && key !== 'sessionParam' && key !== 'stream' && key !== 'methods' && key !== 'maxBody' && key !== 'upgrade') {
102
+ fail(`routes[${index}].${key}`, 'unknown field (typo guard)');
103
+ }
104
+ }
105
+ const { path, scope, sessionParam, stream, methods, maxBody, upgrade } = route;
106
+ if (typeof path !== 'string' || path === '')
107
+ fail(`routes[${index}].path`, 'must be a non-empty string');
108
+ if (!path.startsWith('/'))
109
+ fail(`routes[${index}].path`, `"${path}" must start with "/"`);
110
+ if (path.includes('?'))
111
+ fail(`routes[${index}].path`, `"${path}" must not contain "?"`);
112
+ if (path.includes('#'))
113
+ fail(`routes[${index}].path`, `"${path}" must not contain "#"`);
114
+ if (path.includes('..'))
115
+ fail(`routes[${index}].path`, `"${path}" must not contain ".."`);
116
+ if (/\s/u.test(path))
117
+ fail(`routes[${index}].path`, `"${path}" must not contain whitespace`);
118
+ if (path.startsWith('/_dsh/') || path.startsWith('/api')) {
119
+ fail(`routes[${index}].path`, `"${path}" starts with a host-owned prefix (/_dsh/ or /api) and cannot be bridged`);
120
+ }
121
+ if (scope !== 'session' && scope !== 'server') {
122
+ fail(`routes[${index}].scope`, 'must be "session" or "server"');
123
+ }
124
+ const param = sessionParam === undefined ? 'sessionId' : sessionParam;
125
+ if (typeof param !== 'string' || !SESSION_PARAM_PATTERN.test(param)) {
126
+ fail(`routes[${index}].sessionParam`, `"${String(param)}" must match ${SESSION_PARAM_PATTERN.source}`);
127
+ }
128
+ const streamFlag = stream === undefined ? false : stream;
129
+ if (typeof streamFlag !== 'boolean')
130
+ fail(`routes[${index}].stream`, 'must be a boolean');
131
+ if (seen.has(path))
132
+ fail(`routes[${index}].path`, `duplicate path "${path}"`);
133
+ seen.add(path);
134
+ // PB-9: the forwardable methods — uppercase members of the fixed
135
+ // vocabulary only (method names are case-sensitive on the wire; a
136
+ // lowercase or mixed-case entry is exactly the typo the strictness
137
+ // exists to catch), deduplicated and sorted so order never moves the
138
+ // digest. Omitted stays `undefined` — the digest-preserving choice: a
139
+ // 2.1-era declaration must hash EXACTLY as it did before this field
140
+ // existed, so defaults never enter the canonical form.
141
+ let normalizedMethods;
142
+ if (methods !== undefined) {
143
+ if (!Array.isArray(methods) || methods.length === 0) {
144
+ fail(`routes[${index}].methods`, 'must be a non-empty array of HTTP methods');
145
+ }
146
+ const upper = [];
147
+ for (const entry of methods) {
148
+ if (typeof entry !== 'string' || !BRIDGE_METHODS.includes(entry)) {
149
+ fail(`routes[${index}].methods`, `${JSON.stringify(entry ?? null)} is not one of ${BRIDGE_METHODS.join(', ')}`);
150
+ }
151
+ if (!upper.includes(entry))
152
+ upper.push(entry);
153
+ }
154
+ upper.sort();
155
+ normalizedMethods = upper;
156
+ }
157
+ // PB-9: the body cap — a positive integer, at most
158
+ // {@link MAX_ROUTE_BODY_BYTES}. Same digest rule as `methods`: only an
159
+ // EXPLICIT maxBody enters the canonical form, even when it equals the
160
+ // default.
161
+ let normalizedMaxBody;
162
+ if (maxBody !== undefined) {
163
+ if (typeof maxBody !== 'number' || !Number.isInteger(maxBody) || maxBody <= 0 || maxBody > MAX_ROUTE_BODY_BYTES) {
164
+ fail(`routes[${index}].maxBody`, `must be a positive integer up to ${MAX_ROUTE_BODY_BYTES}`);
165
+ }
166
+ normalizedMaxBody = maxBody;
167
+ }
168
+ // PB-10: the WebSocket tunnel mark. Only `true` is legal (this is a
169
+ // declaration, not a toggle), it cannot coexist with `stream` (a route
170
+ // is one thing), and it is GET-only — an explicit `methods` naming
171
+ // anything else cannot be an upgrade route.
172
+ let normalizedUpgrade;
173
+ if (upgrade !== undefined) {
174
+ if (upgrade !== true)
175
+ fail(`routes[${index}].upgrade`, 'must be true or omitted');
176
+ if (streamFlag)
177
+ fail(`routes[${index}].upgrade`, 'cannot be combined with stream');
178
+ if (normalizedMethods !== undefined && normalizedMethods.some((entry) => entry !== 'GET')) {
179
+ fail(`routes[${index}].upgrade`, 'upgrade routes are GET-only');
180
+ }
181
+ normalizedUpgrade = true;
182
+ }
183
+ return { path, scope, sessionParam: param, stream: streamFlag, methods: normalizedMethods, maxBody: normalizedMaxBody, upgrade: normalizedUpgrade };
184
+ });
185
+ normalizedRoutes.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
186
+ // Fixed key order = the canonical form both ends hash; insertion order of
187
+ // the object literal IS the contract, JSON.stringify never sorts. The
188
+ // route entries carry `methods` / `maxBody` ONLY when the plugin declared
189
+ // them — `JSON.stringify` drops `undefined` values, but the explicit
190
+ // conditional documents the intent and keeps the PB-1 compatibility
191
+ // visible instead of incidental.
192
+ const canonical = JSON.stringify({
193
+ plugin,
194
+ version,
195
+ routes: normalizedRoutes.map((route) => {
196
+ const entry = {
197
+ path: route.path,
198
+ scope: route.scope,
199
+ sessionParam: route.sessionParam,
200
+ stream: route.stream,
201
+ };
202
+ if (route.methods !== undefined)
203
+ entry.methods = route.methods;
204
+ if (route.maxBody !== undefined)
205
+ entry.maxBody = route.maxBody;
206
+ if (route.upgrade !== undefined)
207
+ entry.upgrade = route.upgrade;
208
+ return entry;
209
+ }),
210
+ });
211
+ const digest = createHash('sha256').update(canonical, 'utf8').digest('hex').slice(0, 16);
212
+ return { plugin, version, routes: normalizedRoutes, digest };
213
+ }
214
+ /**
215
+ * Create the registry one DSH process (one plugin row) owns.
216
+ *
217
+ * Overwrite bookkeeping is identity-based, not flag-based: each dispose
218
+ * closes over the exact declaration object it registered and only deletes
219
+ * the table entry while it is STILL that object — a stale dispose meeting a
220
+ * newer registration finds someone else's identity and does nothing.
221
+ */
222
+ export function createBridgeRegistry(options = {}) {
223
+ const byPlugin = new Map();
224
+ const listeners = new Set();
225
+ const warn = options.warn;
226
+ // A throwing listener must not break the register/remove that invoked it —
227
+ // registration is bookkeeping other plugins depend on, a bad observer is
228
+ // that observer's problem. Failures surface through warn when there is one.
229
+ const emit = () => {
230
+ for (const listener of [...listeners]) {
231
+ try {
232
+ listener();
233
+ }
234
+ catch (error) {
235
+ warn?.(`a zenRemote onChange listener failed: ${error instanceof Error ? error.message : String(error)}`);
236
+ }
237
+ }
238
+ };
239
+ return {
240
+ register(input) {
241
+ const normalized = normalizeDeclaration(input);
242
+ const replaced = byPlugin.has(normalized.plugin);
243
+ byPlugin.set(normalized.plugin, normalized);
244
+ if (replaced)
245
+ warn?.(`plugin "${normalized.plugin}" re-registered its bridge declaration; the earlier one was replaced`);
246
+ emit();
247
+ return () => {
248
+ if (byPlugin.get(normalized.plugin) !== normalized)
249
+ return;
250
+ byPlugin.delete(normalized.plugin);
251
+ emit();
252
+ };
253
+ },
254
+ registerNormalized(normalized) {
255
+ const replaced = byPlugin.has(normalized.plugin);
256
+ byPlugin.set(normalized.plugin, normalized);
257
+ if (replaced)
258
+ warn?.(`plugin "${normalized.plugin}" re-registered its bridge entry; the earlier one was replaced`);
259
+ emit();
260
+ return () => {
261
+ if (byPlugin.get(normalized.plugin) !== normalized)
262
+ return;
263
+ byPlugin.delete(normalized.plugin);
264
+ emit();
265
+ };
266
+ },
267
+ list() {
268
+ return [...byPlugin.values()].sort((a, b) => (a.plugin < b.plugin ? -1 : a.plugin > b.plugin ? 1 : 0));
269
+ },
270
+ get(plugin) {
271
+ return byPlugin.get(plugin);
272
+ },
273
+ onChange(listener) {
274
+ listeners.add(listener);
275
+ return () => {
276
+ listeners.delete(listener);
277
+ };
278
+ },
279
+ };
280
+ }
281
+ /**
282
+ * Compare the local registry against the server's handshake table — one
283
+ * {@link BridgeStatus} per LOCAL declaration, in the input order.
284
+ *
285
+ * Judged in this order, first hit wins: offline → the server predates the
286
+ * plugins handshake field (`undefined`, so nothing can match) → the plugin
287
+ * is absent server-side → version differs → version equal but digest
288
+ * differs (same release, different declaration — a skew worth naming) → a
289
+ * `scope:'server'` route without the server owner's consent → bridged.
290
+ *
291
+ * Consent is judged `!== true` on purpose, not `=== false`: an entry without
292
+ * the field (an older server that learned the field later) means UNKNOWN
293
+ * consent, and whole-machine data must stay home until the server says yes.
294
+ */
295
+ export function compareBridges(local, server, online) {
296
+ return local.map((declaration) => {
297
+ const localStatus = { plugin: declaration.plugin, bridged: false, localVersion: declaration.version };
298
+ if (!online)
299
+ return { ...localStatus, reason: 'offline' };
300
+ if (server === undefined)
301
+ return { ...localStatus, reason: 'server-too-old' };
302
+ const entry = server[declaration.plugin];
303
+ if (typeof entry !== 'object' || entry === null)
304
+ return { ...localStatus, reason: 'not-on-server' };
305
+ const record = entry;
306
+ const serverVersion = typeof record.version === 'string' ? record.version : undefined;
307
+ if (serverVersion !== declaration.version)
308
+ return { ...localStatus, reason: 'version-mismatch', serverVersion };
309
+ if (record.digest !== declaration.digest)
310
+ return { ...localStatus, reason: 'declaration-mismatch', serverVersion };
311
+ const hasServerScopeRoute = declaration.routes.some((route) => route.scope === 'server');
312
+ if (hasServerScopeRoute && record.serverScopeConsent !== true) {
313
+ return { ...localStatus, reason: 'consent-off', serverVersion };
314
+ }
315
+ return { plugin: declaration.plugin, bridged: true, localVersion: declaration.version, serverVersion };
316
+ });
317
+ }
318
+ //# sourceMappingURL=plugin-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-bridge.js","sourceRoot":"","sources":["../src/plugin-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * The plugin bridge's loopback executor (PB-3): performs one GET against one
3
+ * of THIS server's own plugin routes, over real loopback HTTP, so the
4
+ * plugin's own handler — its authentication, its req/res semantics — runs
5
+ * exactly as it would for a local browser (docs/adr/0001). The relay route
6
+ * hands this module a validated path + query and gets the plugin's answer
7
+ * back as a status code plus a byte stream to pump at the sub-client.
8
+ *
9
+ * Why the cookie dance: DSH 0.1.2+ requires a signed cookie on EVERY route.
10
+ * The gateway half solved this for browsers by "playing courier" — one
11
+ * `GET /?token=…` (the only URL that accepts a token), then the 303's
12
+ * `set-cookie` straight onto the client (lib/lan-gate-server.cjs
13
+ * attemptAuthExchange). This module is the same courier for the relay: it
14
+ * keeps ONE cookie in memory for the process, exchanges it once on first
15
+ * use, and re-exchanges exactly once when the plugin route answers 401 (a
16
+ * rotated signing key). The exchange is the lan-gate.mjs wiring's shape:
17
+ * `connection.authenticatedUrl(protocol//127.0.0.1:port)` builds the token
18
+ * URL — absent on 0.1.1 hosts, where there is nothing to exchange and every
19
+ * bridged call fails `loopback-auth` honestly.
20
+ *
21
+ * The cookie never leaves this module: no log, no state file, no response —
22
+ * it exists only as the `cookie` request header on the loopback hop.
23
+ *
24
+ * Transport is node:http / node:https on purpose: the plugin's answer must
25
+ * stream (chunk after chunk, with real backpressure) and must die the
26
+ * moment the sub-client hangs up (an AbortSignal reaches the socket), and
27
+ * 0.2.1's self-signed https needs `rejectUnauthorized: false` — a per-
28
+ * request knob fetch cannot express. The loosened verification is safe BY
29
+ * CONSTRUCTION: the connection is dialed to the literal 127.0.0.1 the
30
+ * wiring pinned, never to a name that could be spoofed; the worst an
31
+ * on-path attacker could forge is data from the machine's own web server
32
+ * to itself.
33
+ */
34
+ import http from 'node:http';
35
+ import https from 'node:https';
36
+ import { ADAPTER_RESPONSE_HEADER_ALLOWLIST } from './plugin-adapter-core.js';
37
+ /** The adapter allowlist's headers off one loopback answer, verbatim. */
38
+ function extraAdapterHeadersOf(response) {
39
+ const extra = {};
40
+ for (const name of ADAPTER_RESPONSE_HEADER_ALLOWLIST) {
41
+ const value = response.headers[name];
42
+ if (typeof value === 'string' && value !== '')
43
+ extra[name] = value;
44
+ }
45
+ return extra;
46
+ }
47
+ /** The one failure the relay maps onto 502 `loopback-auth`: the cookie
48
+ * exchange could not run (no token URL on this host) or did not yield a
49
+ * cookie (wrong token, a non-redirect answer, a network error). */
50
+ export class LoopbackAuthError extends Error {
51
+ constructor(message) {
52
+ super(message);
53
+ this.name = 'LoopbackAuthError';
54
+ }
55
+ }
56
+ function isLoopbackAuthError(error) {
57
+ return error instanceof LoopbackAuthError;
58
+ }
59
+ /** One raw loopback request that never follows redirects and resolves with
60
+ * the raw IncomingMessage (the caller drains or streams it). `body`, when
61
+ * given, is pumped into the request verbatim (PB-9's write forwarding); a
62
+ * body dying mid-flight destroys the request, which rejects the promise.
63
+ * The loosened certificate verification (PB-3-fix) applies ONLY to a
64
+ * literal `127.0.0.1` hostname — the plugin hop the wiring pins — never to
65
+ * the token exchange when its URL names anything else. */
66
+ function rawRequest(target, method, pathWithQuery, headers, body, signal) {
67
+ return new Promise((resolve, reject) => {
68
+ const transport = target.protocol === 'https:' ? https : http;
69
+ // The signal reaching request() tears the socket down on abort — the
70
+ // sub-client's hang-up propagates here without extra wiring.
71
+ const request = transport.request({
72
+ protocol: target.protocol,
73
+ hostname: target.hostname,
74
+ port: target.port,
75
+ method,
76
+ path: pathWithQuery,
77
+ headers,
78
+ // 0.2.1 serves self-signed https; the dial is the literal loopback
79
+ // address (see the module comment), so certificate identity carries
80
+ // no information to verify — and ONLY there: any other hostname
81
+ // (the token URL's, should the wiring ever point it elsewhere) keeps
82
+ // full verification.
83
+ ...(target.protocol === 'https:' && target.hostname === '127.0.0.1' ? { rejectUnauthorized: false } : {}),
84
+ signal,
85
+ }, resolve);
86
+ request.on('error', reject);
87
+ if (body === null) {
88
+ request.end();
89
+ return;
90
+ }
91
+ // The body streams through as-is: node's pipe owns the backpressure and
92
+ // ends the request when the body ends. A source error (the relay
93
+ // request's socket dying) tears the loopback request down, which
94
+ // rejects through the handler above.
95
+ body.on('error', (error) => {
96
+ request.destroy(error);
97
+ });
98
+ body.pipe(request);
99
+ });
100
+ }
101
+ /** The `name=value` pair of the FIRST set-cookie header, or undefined. */
102
+ function cookiePairOf(setCookies) {
103
+ const first = setCookies?.[0];
104
+ if (first === undefined)
105
+ return undefined;
106
+ const pair = first.split(';', 1)[0] ?? '';
107
+ return pair.includes('=') && pair !== '' ? pair : undefined;
108
+ }
109
+ /**
110
+ * Build the loopback executor. The cookie lives only in this closure.
111
+ */
112
+ export function createPluginLoopback(options) {
113
+ let cookie;
114
+ /** One `GET <tokenUrl>` (never redirected — node:http already does not
115
+ * follow): a 3xx carrying a set-cookie yields the pair; anything else is
116
+ * an exchange failure. The token response body is always drained and
117
+ * discarded — it is the DSH homepage, none of this module's business. */
118
+ const exchangeCookie = async (signal) => {
119
+ const tokenUrl = options.getTokenUrl();
120
+ if (tokenUrl === undefined)
121
+ throw new LoopbackAuthError('this host exposes no authenticated token URL');
122
+ let url;
123
+ try {
124
+ url = new URL(tokenUrl);
125
+ }
126
+ catch {
127
+ throw new LoopbackAuthError('the token URL does not parse');
128
+ }
129
+ let response;
130
+ try {
131
+ response = await rawRequest({ protocol: url.protocol, hostname: url.hostname, port: Number(url.port) || (url.protocol === 'https:' ? 443 : 80) }, 'GET', `${url.pathname}${url.search}`, { accept: 'text/html' }, null, signal);
132
+ }
133
+ catch (error) {
134
+ if (signal.aborted)
135
+ throw error;
136
+ throw new LoopbackAuthError(`the token exchange request failed: ${error instanceof Error ? error.message : String(error)}`);
137
+ }
138
+ const status = response.statusCode ?? 0;
139
+ const pair = cookiePairOf(response.headers['set-cookie']);
140
+ response.resume();
141
+ if (status >= 300 && status < 400 && pair !== undefined) {
142
+ cookie = pair;
143
+ return;
144
+ }
145
+ throw new LoopbackAuthError(`the token exchange answered ${status}${pair === undefined ? ' without a cookie' : ''}`);
146
+ };
147
+ return {
148
+ async cookieHeader() {
149
+ if (cookie === undefined)
150
+ await exchangeCookie(new AbortController().signal);
151
+ return cookie ?? '';
152
+ },
153
+ async request(method, path, query, headers, body, signal) {
154
+ const target = options.getTarget();
155
+ const pathWithQuery = query === '' ? path : `${path}?${query}`;
156
+ // A body cannot be replayed: the first attempt pumps it into the
157
+ // loopback socket, and a 401 arriving afterwards leaves nothing to
158
+ // retry with. So a body-bearing request WARMS the cookie first (one
159
+ // exchange, no plugin request), and a 401 after that travels verbatim
160
+ // — the plugin's own auth answer, like any other status. Bodyless
161
+ // requests keep the full 401 → re-exchange → retry ladder.
162
+ if (body !== null && cookie === undefined) {
163
+ await exchangeCookie(signal);
164
+ }
165
+ const attempt = async () => {
166
+ const requestHeaders = { host: `127.0.0.1:${target.port}` };
167
+ if (headers.accept !== undefined)
168
+ requestHeaders.accept = headers.accept;
169
+ if (headers.range !== undefined)
170
+ requestHeaders.range = headers.range;
171
+ // PB-9: the forwarded body's content type — the ONE body header the
172
+ // bridge passes through.
173
+ if (headers.contentType !== undefined)
174
+ requestHeaders['content-type'] = headers.contentType;
175
+ // The cookie rides ONLY here. An absent cookie still attempts the
176
+ // route — a plugin route that skipped DSH auth answers honestly.
177
+ if (cookie !== undefined)
178
+ requestHeaders.cookie = cookie;
179
+ return rawRequest({ protocol: target.protocol, hostname: '127.0.0.1', port: target.port }, method, pathWithQuery, requestHeaders, body, signal);
180
+ };
181
+ let response = await attempt();
182
+ if (response.statusCode === 401 && body === null) {
183
+ // Stale or missing cookie: exchange ONCE and retry. A 401 after the
184
+ // fresh cookie is the plugin's own answer — it travels to the
185
+ // sub-client verbatim, like any other status.
186
+ response.resume();
187
+ cookie = undefined;
188
+ await exchangeCookie(signal);
189
+ response = await attempt();
190
+ }
191
+ return {
192
+ status: response.statusCode ?? 0,
193
+ contentType: typeof response.headers['content-type'] === 'string' ? response.headers['content-type'] : undefined,
194
+ contentLength: typeof response.headers['content-length'] === 'string' ? response.headers['content-length'] : undefined,
195
+ cacheControl: typeof response.headers['cache-control'] === 'string' ? response.headers['cache-control'] : undefined,
196
+ // PB-12: the adapter allowlist — the HTML preview's CSP sandbox, a
197
+ // download's content-disposition, range headers. Collected here so
198
+ // the adapter forwards can hand them to the browser; the relay's
199
+ // cooperative branch ignores the field.
200
+ extraHeaders: extraAdapterHeadersOf(response),
201
+ stream: response,
202
+ };
203
+ },
204
+ };
205
+ }
206
+ export { isLoopbackAuthError };
207
+ //# sourceMappingURL=plugin-loopback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-loopback.js","sourceRoot":"","sources":["../src/plugin-loopback.ts"],"names":[],"mappings":"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"}