@dsh-plus/access-gate 0.1.3 → 0.1.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 +38 -27
- package/package.json +8 -9
- package/src/client/card.tsx +9 -16
- package/src/client/client.ts +13 -13
package/lib/client.js
CHANGED
|
@@ -196,14 +196,15 @@ window.__ModuleLoader__.load({
|
|
|
196
196
|
//#endregion
|
|
197
197
|
//#region ../shared/src/client/scope.ts
|
|
198
198
|
/**
|
|
199
|
-
*
|
|
199
|
+
* remote.settings 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
200
200
|
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
201
201
|
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
202
202
|
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
203
203
|
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
204
204
|
* effect 归属调用方 fiber(label 需含插件名以便诊断)。
|
|
205
205
|
*/
|
|
206
|
-
function
|
|
206
|
+
function createSettingsScope(c, ns, label) {
|
|
207
|
+
const remote = c.get("remote");
|
|
207
208
|
let state = {
|
|
208
209
|
status: "loading",
|
|
209
210
|
value: void 0,
|
|
@@ -218,36 +219,37 @@ window.__ModuleLoader__.load({
|
|
|
218
219
|
};
|
|
219
220
|
const load = async () => {
|
|
220
221
|
const gen = ++generation;
|
|
221
|
-
let
|
|
222
|
+
let view;
|
|
222
223
|
try {
|
|
223
|
-
response = await
|
|
224
|
+
const response = await remote.settings.describe();
|
|
225
|
+
if (!response.ok) return;
|
|
226
|
+
view = response.value;
|
|
224
227
|
} catch {
|
|
225
228
|
return;
|
|
226
229
|
}
|
|
227
|
-
if (gen !== generation
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
if (view === void 0) {
|
|
230
|
+
if (gen !== generation) return;
|
|
231
|
+
const namespace = view.namespaces.find((candidate) => candidate.ns === ns);
|
|
232
|
+
if (namespace === void 0) {
|
|
231
233
|
publish({
|
|
232
234
|
status: "unavailable",
|
|
233
235
|
value: void 0,
|
|
234
236
|
revision: void 0,
|
|
235
|
-
writable
|
|
237
|
+
writable: view.writable
|
|
236
238
|
});
|
|
237
239
|
return;
|
|
238
240
|
}
|
|
239
241
|
publish({
|
|
240
242
|
status: "ready",
|
|
241
|
-
value:
|
|
242
|
-
revision:
|
|
243
|
-
writable
|
|
243
|
+
value: namespace.value,
|
|
244
|
+
revision: namespace.revision,
|
|
245
|
+
writable: view.writable
|
|
244
246
|
});
|
|
245
247
|
};
|
|
246
248
|
const refresh = (namespace) => {
|
|
247
249
|
if (namespace !== void 0 && namespace !== ns) return;
|
|
248
250
|
load();
|
|
249
251
|
};
|
|
250
|
-
const disposers = [
|
|
252
|
+
const disposers = [remote.$on("settings/document-updated", refresh), c.on("connection/reset", () => {
|
|
251
253
|
load();
|
|
252
254
|
})];
|
|
253
255
|
c.effect(() => () => {
|
|
@@ -265,6 +267,20 @@ window.__ModuleLoader__.load({
|
|
|
265
267
|
load
|
|
266
268
|
};
|
|
267
269
|
}
|
|
270
|
+
/** 以 remote.settings 构造命名空间绑定的 api 面。 */
|
|
271
|
+
function createNamespaceApi(remote, ns) {
|
|
272
|
+
return {
|
|
273
|
+
update: async (patch, expectedRevision) => {
|
|
274
|
+
const response = await remote.update(ns, patch, expectedRevision);
|
|
275
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings update failed");
|
|
276
|
+
},
|
|
277
|
+
describe: async () => {
|
|
278
|
+
const response = await remote.describe();
|
|
279
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings describe failed");
|
|
280
|
+
return response.value;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
268
284
|
//#endregion
|
|
269
285
|
//#region ../shared/src/client/styles.ts
|
|
270
286
|
/**
|
|
@@ -438,10 +454,10 @@ ${extra}
|
|
|
438
454
|
}, [value, draft]);
|
|
439
455
|
(0, react.useEffect)(() => {
|
|
440
456
|
let alive = true;
|
|
441
|
-
api.describe(
|
|
442
|
-
if (!alive
|
|
443
|
-
const
|
|
444
|
-
setTokenConfigured(
|
|
457
|
+
api.describe().then((view) => {
|
|
458
|
+
if (!alive) return;
|
|
459
|
+
const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS);
|
|
460
|
+
setTokenConfigured(ns?.secrets.some((secret) => secret.path.join(".") === "token" && secret.set) ?? false);
|
|
445
461
|
}).catch(() => {});
|
|
446
462
|
return () => {
|
|
447
463
|
alive = false;
|
|
@@ -478,12 +494,7 @@ ${extra}
|
|
|
478
494
|
const patch = toPatch(draft);
|
|
479
495
|
if (draft.token !== "") patch.token = draft.token;
|
|
480
496
|
const revision = scope.getSnapshot().revision;
|
|
481
|
-
api.update({
|
|
482
|
-
ns: SETTINGS_NS,
|
|
483
|
-
patch,
|
|
484
|
-
...revision !== void 0 ? { expectedRevision: revision } : {}
|
|
485
|
-
}).then(async (response) => {
|
|
486
|
-
if (!response.result.ok) throw new Error(response.result.error?.message ?? t("saveFailed"));
|
|
497
|
+
api.update(patch, revision).then(async () => {
|
|
487
498
|
await scope.load();
|
|
488
499
|
const next = scope.getSnapshot().value;
|
|
489
500
|
if (next !== void 0) setDraft(draftFromValue(next));
|
|
@@ -787,8 +798,8 @@ ${extra}
|
|
|
787
798
|
const inject = [
|
|
788
799
|
"slots",
|
|
789
800
|
"locale",
|
|
790
|
-
"
|
|
791
|
-
"remote"
|
|
801
|
+
"remote",
|
|
802
|
+
"remote.settings"
|
|
792
803
|
];
|
|
793
804
|
function apply(ctx) {
|
|
794
805
|
const c = ctx;
|
|
@@ -800,8 +811,8 @@ ${extra}
|
|
|
800
811
|
zh,
|
|
801
812
|
en
|
|
802
813
|
}), "access-gate: locale");
|
|
803
|
-
const
|
|
804
|
-
const
|
|
814
|
+
const scope = createSettingsScope(c, SETTINGS_NS, "access-gate: settings scope");
|
|
815
|
+
const api = createNamespaceApi(c.get("remote").settings, SETTINGS_NS);
|
|
805
816
|
c.slots.inject("settings.plugin.item", () => c.slots.register({
|
|
806
817
|
name: "settings.plugin.item",
|
|
807
818
|
key: SETTINGS_NS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/access-gate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: dsh web 全量访问围栏——本机直连永久放行,远程按白名单 IP(XFF 还原)或 token(登录页 + cookie)放行",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,28 +23,27 @@
|
|
|
23
23
|
"dsh": {
|
|
24
24
|
"client": {
|
|
25
25
|
"inject": [
|
|
26
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
27
26
|
"@deepseek-ai/dsh-client-locale",
|
|
28
|
-
"@deepseek-ai/dsh-client-
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
29
28
|
"@deepseek-ai/dsh-api-remotes"
|
|
30
29
|
],
|
|
31
30
|
"platform": "web"
|
|
32
31
|
}
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
|
-
"@dsh-plus/shared": "0.1.
|
|
34
|
+
"@dsh-plus/shared": "0.1.6"
|
|
36
35
|
},
|
|
37
36
|
"peerDependencies": {
|
|
38
37
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
39
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
40
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
38
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.2-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.1",
|
|
41
40
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@deepseek-ai/cordis": "4.0.1",
|
|
45
|
-
"@deepseek-ai/dsh-home-paths": "0.1.
|
|
46
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
47
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
44
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.1",
|
|
48
47
|
"@deepseek-ai/schemastery": "3.18.1",
|
|
49
48
|
"@types/react": "~18.3.1",
|
|
50
49
|
"react": "^18.2.0"
|
package/src/client/card.tsx
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
type CardStatusState,
|
|
15
15
|
CheckRow,
|
|
16
16
|
IDLE_STATUS,
|
|
17
|
+
type NamespaceSettingsApi,
|
|
17
18
|
type Scope,
|
|
18
|
-
type SettingsApi,
|
|
19
19
|
TextField,
|
|
20
20
|
} from '@dsh-plus/shared/client'
|
|
21
21
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
@@ -24,7 +24,7 @@ import { SETTINGS_NS } from '../ns.ts'
|
|
|
24
24
|
export interface CardProps {
|
|
25
25
|
t(key: string): string
|
|
26
26
|
scope: Scope
|
|
27
|
-
api:
|
|
27
|
+
api: NamespaceSettingsApi
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** settings 命名空间的脱敏解析值(token 被脱敏剥除)。 */
|
|
@@ -126,12 +126,12 @@ export function AccessGateCard(props: CardProps): ReactElement | null {
|
|
|
126
126
|
useEffect(() => {
|
|
127
127
|
let alive = true
|
|
128
128
|
api
|
|
129
|
-
.describe(
|
|
130
|
-
.then((
|
|
131
|
-
if (!alive
|
|
132
|
-
const
|
|
129
|
+
.describe()
|
|
130
|
+
.then((view) => {
|
|
131
|
+
if (!alive) return
|
|
132
|
+
const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS)
|
|
133
133
|
setTokenConfigured(
|
|
134
|
-
|
|
134
|
+
ns?.secrets.some((secret) => secret.path.join('.') === 'token' && secret.set) ?? false,
|
|
135
135
|
)
|
|
136
136
|
})
|
|
137
137
|
.catch(() => {})
|
|
@@ -192,15 +192,8 @@ export function AccessGateCard(props: CardProps): ReactElement | null {
|
|
|
192
192
|
if (draft.token !== '') patch.token = draft.token
|
|
193
193
|
const revision = scope.getSnapshot().revision
|
|
194
194
|
api
|
|
195
|
-
.update(
|
|
196
|
-
|
|
197
|
-
patch,
|
|
198
|
-
...(revision !== undefined ? { expectedRevision: revision } : {}),
|
|
199
|
-
})
|
|
200
|
-
.then(async (response) => {
|
|
201
|
-
if (!response.result.ok) {
|
|
202
|
-
throw new Error(response.result.error?.message ?? t('saveFailed'))
|
|
203
|
-
}
|
|
195
|
+
.update(patch, revision)
|
|
196
|
+
.then(async () => {
|
|
204
197
|
await scope.load()
|
|
205
198
|
const next = scope.getSnapshot().value as ConfigValue | undefined
|
|
206
199
|
if (next !== undefined) setDraft(draftFromValue(next))
|
package/src/client/client.ts
CHANGED
|
@@ -3,17 +3,21 @@
|
|
|
3
3
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
4
4
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
5
5
|
*
|
|
6
|
-
* 类型说明:浏览器半只用到 slots/locale/
|
|
6
|
+
* 类型说明:浏览器半只用到 slots/locale/remote 的很窄一面,
|
|
7
7
|
* scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现)。
|
|
8
|
-
*
|
|
8
|
+
* 该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
|
|
9
9
|
* (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
|
|
10
10
|
* 官方配置页按此 key 与 Host 已注册命名空间配对分发。
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
|
|
12
|
+
* 已移除;不复用 settingsScope 服务——非 loopback 页面下它固定 memory 模式无数据)。
|
|
13
13
|
* @module @dsh-plus/access-gate/client
|
|
14
14
|
*/
|
|
15
15
|
import type { Context } from '@deepseek-ai/cordis'
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
createNamespaceApi,
|
|
18
|
+
createSettingsScope,
|
|
19
|
+
type SettingsRemoteFace,
|
|
20
|
+
} from '@dsh-plus/shared/client'
|
|
17
21
|
import { SETTINGS_NS } from '../ns.ts'
|
|
18
22
|
import { AccessGateCard } from './card.tsx'
|
|
19
23
|
import { en, NS, zh } from './i18n.ts'
|
|
@@ -22,7 +26,7 @@ import { injectStyle } from './styles.ts'
|
|
|
22
26
|
export const name = 'dsh-plus-access-gate'
|
|
23
27
|
|
|
24
28
|
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
25
|
-
export const inject = ['slots', 'locale', '
|
|
29
|
+
export const inject = ['slots', 'locale', 'remote', 'remote.settings'] as const
|
|
26
30
|
|
|
27
31
|
interface SlotsLike {
|
|
28
32
|
inject(key: string, callback: () => unknown): unknown
|
|
@@ -35,17 +39,13 @@ interface LocaleLike {
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
interface RemoteLike {
|
|
42
|
+
settings: SettingsRemoteFace
|
|
38
43
|
$on(event: string, listener: (payload?: unknown) => void): () => void
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
interface ConnectionLike {
|
|
42
|
-
api: { settings: Parameters<typeof createApiScope>[0] }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
46
|
interface ClientContext {
|
|
46
47
|
slots: SlotsLike
|
|
47
48
|
locale: LocaleLike
|
|
48
|
-
get(key: 'connection'): ConnectionLike
|
|
49
49
|
get(key: 'remote'): RemoteLike
|
|
50
50
|
on(event: string, listener: () => void): () => void
|
|
51
51
|
effect(execute: () => () => void, label?: string): unknown
|
|
@@ -61,8 +61,8 @@ export function apply(ctx: Context): void {
|
|
|
61
61
|
'access-gate: style',
|
|
62
62
|
)
|
|
63
63
|
c.effect(() => c.locale.register(NS, { zh, en }), 'access-gate: locale')
|
|
64
|
-
const
|
|
65
|
-
const
|
|
64
|
+
const scope = createSettingsScope(c, SETTINGS_NS, 'access-gate: settings scope')
|
|
65
|
+
const api = createNamespaceApi(c.get('remote').settings, SETTINGS_NS)
|
|
66
66
|
c.slots.inject('settings.plugin.item', () =>
|
|
67
67
|
c.slots.register(
|
|
68
68
|
{
|