dsh-scnet 0.6.9 → 0.6.10

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 CHANGED
@@ -4,17 +4,15 @@
4
4
 
5
5
  DSH-SCNet (`dsh-scnet` on npm) is a community-maintained DSH bundle for operating Supercomputing Network (SCNet). It packages the source [`scnet-hpc`](https://github.com/lql341/scnet-hpc) Skill, profile-aware utilities, and bounded tools for SSH, Slurm, OpenAPI files, regional resources, and Notebook discovery.
6
6
 
7
- Current release: **0.6.9**
7
+ Current release: **0.6.10**
8
8
 
9
9
  <!-- scnet-release:start -->
10
- ## 0.6.9 highlights
11
-
12
- - Default CLI output is compact across OpenAPI and SSH, with `--details` and `--raw` for
13
- progressively richer responses and bounded internal job waiting to reduce repeated output.
14
- - OpenAPI completion is confirmed against accounting before it becomes terminal, and SSH job
15
- lookup falls back to accounting after a job leaves the live queue.
16
- - First-use setup defaults to OpenAPI, retains existing backend preferences, and preserves
17
- unrelated configuration fields when settings are updated.
10
+ ## 0.6.10 highlights
11
+
12
+ - Coordinate the synchronized downstream DSH release with a protocol-neutral one-shot loopback
13
+ OAuth callback listener, fixed-path validation, timeout cleanup, and generic browser responses.
14
+ - Keep the canonical Skill endpoint-neutral; no SCNet issuer, endpoint, client ID, scope, or
15
+ credential is embedded in this release.
18
16
  <!-- scnet-release:end -->
19
17
 
20
18
  > This is an independent community project. It is compatible with DeepSeek Harness but is not an official DeepSeek product and does not imply endorsement, partnership, or authorization by DeepSeek.
package/README_CN.md CHANGED
@@ -4,15 +4,15 @@
4
4
 
5
5
  <p align="center">在 DeepSeek Harness 里操作超算互联网(SCNet):配置 SSH、管理 Slurm 作业、查询 OpenAPI 区域和文件、检查 Notebook 资源与实例,并保留海光 DCU/DTK 诊断能力。</p>
6
6
 
7
- 当前版本:**0.6.9**
7
+ 当前版本:**0.6.10**
8
8
 
9
9
  <!-- scnet-release:start -->
10
- ## 0.6.9 更新
10
+ ## 0.6.10 更新
11
11
 
12
- - OpenAPI 与 SSH 的 CLI 默认使用精简输出;需要更多信息时使用 `--details` 或 `--raw`,
13
- 作业等待改为程序内部有界轮询,减少重复输出。
14
- - OpenAPI 在作业状态终结前通过账本确认,SSH 作业离开实时队列后自动回退到账本查询。
15
- - 首次配置默认选择 OpenAPI,已有用户保留原 backend 偏好,更新设置时保留无关配置字段。
12
+ - 协调下游 DSH 发布协议无关的一次性 loopback OAuth 回调监听器,包含固定路径校验、
13
+ 超时清理和不暴露回调细节的通用浏览器响应。
14
+ - canonical Skill 继续保持 endpoint 无关;本版本不嵌入 SCNet issuer、endpoint、client ID、
15
+ scope 或任何凭证。
16
16
  <!-- scnet-release:end -->
17
17
 
18
18
  > 本项目是独立维护的社区项目,与 DeepSeek Harness 兼容,但不是 DeepSeek 官方产品,也不表示 DeepSeek 对本项目提供背书、合作或授权。
@@ -0,0 +1,155 @@
1
+ import { createServer } from 'node:http'
2
+
3
+ import { ConnectorError } from './errors.mjs'
4
+
5
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', '[::1]', 'localhost'])
6
+ const CALLBACK_TIMEOUT_MS = 120000
7
+
8
+ function requiredString(value, label) {
9
+ const normalized = String(value ?? '').trim()
10
+ if (!normalized) throw new TypeError(`${label} must be a non-empty string`)
11
+ return normalized
12
+ }
13
+
14
+ function parseLoopbackRedirect(value) {
15
+ let redirect
16
+ try {
17
+ redirect = new URL(requiredString(value, 'redirectUri'))
18
+ } catch {
19
+ throw new TypeError('redirectUri must be a valid URL')
20
+ }
21
+ if (redirect.protocol !== 'http:' || !LOOPBACK_HOSTS.has(redirect.hostname)) {
22
+ throw new TypeError('redirectUri must use an http loopback address')
23
+ }
24
+ if (!redirect.pathname || redirect.pathname === '/') {
25
+ throw new TypeError('redirectUri must include a callback path')
26
+ }
27
+ if (redirect.username || redirect.password || redirect.hash) {
28
+ throw new TypeError('redirectUri must not include credentials or a fragment')
29
+ }
30
+ return redirect
31
+ }
32
+
33
+ function responseBody(res, statusCode, body) {
34
+ const content = Buffer.from(body, 'utf8')
35
+ res.writeHead(statusCode, {
36
+ 'content-type': 'text/html; charset=utf-8',
37
+ 'content-length': content.byteLength,
38
+ connection: 'close',
39
+ })
40
+ res.end(content)
41
+ }
42
+
43
+ /**
44
+ * Start a one-shot local OAuth callback listener.
45
+ *
46
+ * The listener is protocol-neutral: the caller owns the authorization session
47
+ * and provides its callback consumer. Browser responses never contain callback
48
+ * parameters or private error details.
49
+ */
50
+ export async function listenLoopbackCallback({
51
+ redirectUri,
52
+ consumeCallback,
53
+ timeoutMs = CALLBACK_TIMEOUT_MS,
54
+ } = {}) {
55
+ const requested = parseLoopbackRedirect(redirectUri)
56
+ if (typeof consumeCallback !== 'function') {
57
+ throw new TypeError('consumeCallback must be a function')
58
+ }
59
+ const timeout = Number(timeoutMs)
60
+ if (!Number.isFinite(timeout) || timeout <= 0) {
61
+ throw new TypeError('timeoutMs must be a positive number')
62
+ }
63
+
64
+ let settled = false
65
+ let timer
66
+ let actualRedirect
67
+ let resolveResult
68
+ let rejectResult
69
+ const result = new Promise((resolve, reject) => {
70
+ resolveResult = resolve
71
+ rejectResult = reject
72
+ })
73
+
74
+ const server = createServer((req, res) => {
75
+ if (settled) {
76
+ responseBody(res, 410, '<!doctype html><title>Callback closed</title>')
77
+ return
78
+ }
79
+ const callbackUrl = actualRedirect && new URL(req.url || '/', actualRedirect.origin)
80
+ if (req.method !== 'GET' || !callbackUrl || callbackUrl.pathname !== requested.pathname) {
81
+ responseBody(res, 404, '<!doctype html><title>Not found</title>')
82
+ return
83
+ }
84
+ settle(callbackUrl.href, res)
85
+ })
86
+
87
+ function closeServer() {
88
+ if (timer) clearTimeout(timer)
89
+ if (server.listening) server.close()
90
+ }
91
+
92
+ function settle(callbackUrl, res) {
93
+ if (settled) return
94
+ settled = true
95
+ try {
96
+ const value = consumeCallback(callbackUrl)
97
+ responseBody(res, 200, '<!doctype html><title>Authorization complete</title>')
98
+ resolveResult(value)
99
+ } catch (error) {
100
+ responseBody(res, 400, '<!doctype html><title>Authorization failed</title>')
101
+ rejectResult(
102
+ error instanceof ConnectorError
103
+ ? error
104
+ : new ConnectorError('CALLBACK_MISMATCH', error?.message || 'OAuth callback rejected', {
105
+ operation: 'login',
106
+ }),
107
+ )
108
+ } finally {
109
+ closeServer()
110
+ }
111
+ }
112
+
113
+ await new Promise((resolve, reject) => {
114
+ server.once('error', reject)
115
+ server.listen({
116
+ host: requested.hostname,
117
+ port: requested.port,
118
+ exclusive: true,
119
+ }, resolve)
120
+ }).catch((error) => {
121
+ closeServer()
122
+ throw new ConnectorError('CALLBACK_MISMATCH', 'Unable to open the local OAuth callback listener', {
123
+ operation: 'login',
124
+ })
125
+ })
126
+
127
+ const address = server.address()
128
+ if (!address || typeof address === 'string') {
129
+ closeServer()
130
+ throw new ConnectorError('CALLBACK_MISMATCH', 'Local OAuth callback listener returned no address', {
131
+ operation: 'login',
132
+ })
133
+ }
134
+ actualRedirect = new URL(requested.href)
135
+ actualRedirect.port = String(address.port)
136
+
137
+ timer = setTimeout(() => {
138
+ if (settled) return
139
+ settled = true
140
+ rejectResult(new ConnectorError('CALLBACK_MISMATCH', 'OAuth callback timed out', { operation: 'login' }))
141
+ closeServer()
142
+ }, timeout)
143
+ timer.unref?.()
144
+
145
+ return Object.freeze({
146
+ redirectUri: actualRedirect.href,
147
+ waitForCallback: () => result,
148
+ close() {
149
+ if (settled) return
150
+ settled = true
151
+ rejectResult(new ConnectorError('CALLBACK_MISMATCH', 'OAuth callback listener closed', { operation: 'login' }))
152
+ closeServer()
153
+ },
154
+ })
155
+ }
@@ -19,6 +19,10 @@ export {
19
19
  createCredentialStoreAdapter,
20
20
  } from './credentials.mjs'
21
21
 
22
+ export {
23
+ listenLoopbackCallback,
24
+ } from './callback.mjs'
25
+
22
26
  export {
23
27
  MemoryCredentialStore,
24
28
  TokenLifecycle,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-scnet",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "Community DeepSeek Harness bundle for SCNet SSH and OpenAPI jobs, files, Notebook resources, Slurm diagnostics, and Hygon DCU/DTK guidance.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
@@ -1 +1 @@
1
- 0.6.9
1
+ 0.6.10