@goodandready/dsh-lanmode 0.3.1 → 0.4.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 (3) hide show
  1. package/README.md +14 -2
  2. package/lib/shim.js +67 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -35,11 +35,23 @@ Everything happens through the web server's official index tap: one script is in
35
35
 
36
36
  | Piece | Setting | What it does |
37
37
  |---|---|---|
38
- | Settings | `settings` | Hands the two settings packages a connection whose `isLoopback` reads `true`. Everything downstream — the shared mirror, every namespace scope, the core's own pages then behaves as it does on localhost. |
38
+ | Settings | `settings` | Hands every package a connection whose `isLoopback` reads `true`. The shared mirror, every namespace scope, the core's own pages and every plugin's settings section then behave as they do on localhost. |
39
39
  | `crypto.randomUUID` | `randomUuid` | Provides an RFC 4122 v4 implementation over `crypto.getRandomValues`, which insecure origins do have. A no-op where the real one exists. |
40
40
  | `navigator.clipboard` | `clipboard` | Provides a `writeText` fallback so the copy buttons keep working. A no-op where the real one exists. |
41
41
 
42
- The settings substitution is deliberately narrow. Three packages read that flag, and the third is deliverables, where it decides whether a produced file may be opened locally forcing it there would ask the Host to open paths on the server's desktop. Only the settings packages see the substitute.
42
+ One package is excluded on purpose: deliverables, where the flag decides whether a produced file may be opened locally. Forcing it there would ask the Host to open paths on the server's desktop. Nothing else in the web UI reads the flag.
43
+
44
+ The exclusion list replaced an allow list, and the reason is worth writing down. A namespace scope is bound like this:
45
+
46
+ ```js
47
+ bind(spec) {
48
+ const ctx = this.ctx // the caller's context
49
+ const connection = ctx.get('connection')
50
+ ... connection.isLoopback ? 'host' : 'memory'
51
+ }
52
+ ```
53
+
54
+ `this.ctx` belongs to whichever plugin calls `bind`, not to the settings package. Handing the substitute to the settings packages alone therefore fixed the shared mirror and the core's own pages, while every plugin's own settings section still went to memory mode and reported that the harness had not announced its settings.
43
55
 
44
56
  ## Two modes
45
57
 
package/lib/shim.js CHANGED
@@ -67,35 +67,83 @@
67
67
  // ------------------------------------------------------------- настройки
68
68
  if (!options.settings) return
69
69
 
70
- // Флаг читают трое, и третьему подменять нельзя: в результатах работы он
71
- // решает, можно ли открыть файл локально, и с подменой браузер просил бы
72
- // открыть путь на машине сервера. Поэтому — только два настроечных пакета.
73
- var TARGETS = [
74
- '@deepseek-ai/dsh-client-ui-settings',
75
- '@deepseek-ai/dsh-client-ui-settings-general',
70
+ // Флаг живёт на объекте соединения, и читают его в двух совершенно разных
71
+ // местах.
72
+ //
73
+ // Общий справочник разделов заводится в apply настроечного пакета — там
74
+ // хватало подмены контекста. Но каждый отдельный раздел привязывается иначе:
75
+ //
76
+ // bind(spec) {
77
+ // const ctx = this.ctx // контекст вызывающего
78
+ // const connection = ctx.get('connection')
79
+ // ... connection.isLoopback ? 'host' : 'memory'
80
+ // }
81
+ //
82
+ // this.ctx — контекст того плагина, который зовёт bind. Подменять контекст
83
+ // каждому плагину бесполезно: сервис запоминает не заместителя, а исходный
84
+ // контекст, через который его завели. Поэтому подменяется не контекст, а сам
85
+ // объект соединения, и ровно один раз: он один на весь интерфейс, и после
86
+ // подмены одинаковый ответ видят все — и ядро, и любой плагин со своим
87
+ // разделом настроек.
88
+ //
89
+ // Исключение одно: выдача результатов работы. Там флаг решает, можно ли
90
+ // открыть файл локально, и с подменой браузер просил бы открыть путь на
91
+ // машине сервера. Этому пакету возвращается настоящее значение. Больше флаг
92
+ // во всём интерфейсе не читает никто.
93
+ var EXCLUDED = [
94
+ '@deepseek-ai/dsh-client-ui-deliverables',
76
95
  ]
77
96
 
78
- function connectionWithForcedFlag(connection) {
97
+ /** Настоящий ответ по тому же правилу, что и у ядра: только свои адреса. */
98
+ function realLoopback() {
99
+ var host = location.hostname
100
+ if (host === 'localhost' || host === '[::1]' || host === '::1') return true
101
+ return /^127\./.test(host)
102
+ }
103
+
104
+ // Подмена ставится при первой же возможности: как только у кого-то из
105
+ // пакетов появился контекст, из которого достаётся соединение.
106
+ var forced = false
107
+
108
+ function forceOnConnection(ctx) {
109
+ if (forced) return
110
+ var connection = null
111
+ try { connection = ctx && ctx.get && ctx.get('connection') } catch (noService) { connection = null }
112
+ if (!connection || typeof connection !== 'object') return
113
+ try {
114
+ Object.defineProperty(connection, 'isLoopback', {
115
+ configurable: true,
116
+ get: function () { return loopbackAnswer },
117
+ })
118
+ forced = true
119
+ } catch (cannotDefine) {
120
+ // Свойство неподатливо — оставляем как было: страница будет вести себя
121
+ // так же, как без плагина.
122
+ }
123
+ }
124
+
125
+ function connectionWithRealFlag(connection) {
79
126
  if (!connection || typeof connection !== 'object') return connection
80
127
  return new Proxy(connection, {
81
128
  get: function (target, prop) {
82
- if (prop === 'isLoopback') return loopbackAnswer
129
+ if (prop === 'isLoopback') return realLoopback()
83
130
  var value = Reflect.get(target, prop, target)
84
131
  return typeof value === 'function' ? value.bind(target) : value
85
132
  },
86
133
  })
87
134
  }
88
135
 
89
- function ctxWithForcedConnection(ctx) {
136
+ /** Контекст исключённого пакета: ему возвращается настоящее значение. */
137
+ function ctxWithRealConnection(ctx) {
90
138
  return new Proxy(ctx, {
91
139
  get: function (target, prop) {
92
140
  if (prop === 'get') {
93
141
  return function (nameRequested) {
94
142
  var value = target.get(nameRequested)
95
- return nameRequested === 'connection' ? connectionWithForcedFlag(value) : value
143
+ return nameRequested === 'connection' ? connectionWithRealFlag(value) : value
96
144
  }
97
145
  }
98
- if (prop === 'connection') return connectionWithForcedFlag(Reflect.get(target, prop, target))
146
+ if (prop === 'connection') return connectionWithRealFlag(Reflect.get(target, prop, target))
99
147
  var value = Reflect.get(target, prop, target)
100
148
  return typeof value === 'function' ? value.bind(target) : value
101
149
  },
@@ -103,8 +151,8 @@
103
151
  }
104
152
 
105
153
  function wrap(registration) {
106
- if (!registration || TARGETS.indexOf(registration.id) === -1) return registration
107
- if (typeof registration.factory !== 'function') return registration
154
+ if (!registration || typeof registration.factory !== 'function') return registration
155
+ var excluded = EXCLUDED.indexOf(registration.id) !== -1
108
156
  var factory = registration.factory
109
157
  var patched = {}
110
158
  for (var key in registration) patched[key] = registration[key]
@@ -113,7 +161,12 @@
113
161
  if (!moduleExports || typeof moduleExports.apply !== 'function') return moduleExports
114
162
  var originalApply = moduleExports.apply
115
163
  moduleExports.apply = function (ctx) {
116
- return originalApply.call(this, ctxWithForcedConnection(ctx))
164
+ // Исключённый пакет читает соединение раньше, чем его успели подменить,
165
+ // — но полагаться на это нельзя, поэтому ему всегда отдаётся
166
+ // заместитель с настоящим значением.
167
+ if (excluded) return originalApply.call(this, ctxWithRealConnection(ctx))
168
+ forceOnConnection(ctx)
169
+ return originalApply.call(this, ctx)
117
170
  }
118
171
  return moduleExports
119
172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-lanmode",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "LAN and reverse-proxy access for the DeepSeek Harness Web UI: returns the settings service on pages that are not localhost, fills in the Web APIs the browser withholds on plain HTTP, and can open a listener of its own so nothing else is needed.",
5
5
  "license": "MIT",
6
6
  "type": "module",