@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.
- package/README.md +14 -2
- package/lib/shim.js +67 -14
- 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
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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' ?
|
|
143
|
+
return nameRequested === 'connection' ? connectionWithRealFlag(value) : value
|
|
96
144
|
}
|
|
97
145
|
}
|
|
98
|
-
if (prop === 'connection') return
|
|
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 ||
|
|
107
|
-
|
|
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
|
-
|
|
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
|
+
"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",
|