@huaqiu/dsh-auth 0.3.18 → 0.3.20

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/lib/index.mjs +14 -10
  2. package/package.json +2 -2
  3. package/src/host.ts +26 -14
package/lib/index.mjs CHANGED
@@ -63,15 +63,19 @@ function resolveHostConfig(config, env = process.env) {
63
63
  };
64
64
  }
65
65
  /**
66
- * Fetches and caches the host (HQ Edge) session. The cache is memory-only with
67
- * a TTL; the loopback GET is cheap, so we never persist the credential.
66
+ * Fetches the host (HQ Edge) session over the loopback `GET` on EVERY call.
67
+ * The GET is in-memory and loopback-cheap, and the DSH browser half polls this
68
+ * resolver every few seconds (client/index.tsx `refreshHost`) — so re-fetching
69
+ * is exactly what makes a host logout (token cleared) become visible within one
70
+ * poll instead of leaving the UI "logged in" until a page reload.
68
71
  *
69
- * Caching rule — **only a usable session is cached**. A host that is up but has
70
- * no credential yet (EDA not logged in, or the login dialog still open) must
71
- * NOT be cached: the operator may complete the login a second later, and a
72
- * cached negative would pin every tool to `needs_auth` for the whole TTL. The
73
- * old code cached whatever it got, so a DSH that booted before EDA
74
- * authenticated stayed "logged out" for up to `hostSessionTtlSeconds` (300s).
72
+ * Caching rule — **a usable (authenticated) session is NEVER held across a
73
+ * clear**. We fetch fresh each time and drop the cache the moment the host
74
+ * reports no credential (logout / not-yet-logged-in). The only thing the cache
75
+ * is now used for is a TRANSIENT ERROR FALLBACK: if the host is momentarily
76
+ * unreachable we return a recent usable session (within `ttlMs`) rather than
77
+ * spuriously logging the operator out — but never past the TTL, or a stale
78
+ * fallback would mask a real logout that happened while the host was down.
75
79
  */
76
80
  var HostSessionResolver = class {
77
81
  baseUrl;
@@ -92,7 +96,6 @@ var HostSessionResolver = class {
92
96
  async resolve() {
93
97
  if (!this.enabled) return null;
94
98
  const now = Date.now();
95
- if (this.cache !== null && now - this.cache.fetchedAt < this.ttlMs) return this.cache.info;
96
99
  const url = `${this.baseUrl}${this.path}`;
97
100
  let info = null;
98
101
  let status = null;
@@ -108,7 +111,8 @@ var HostSessionResolver = class {
108
111
  url,
109
112
  error: String(err)
110
113
  });
111
- info = null;
114
+ if (this.cache !== null && now - this.cache.fetchedAt < this.ttlMs) return this.cache.info;
115
+ return null;
112
116
  }
113
117
  if (info !== null && info.authenticated) {
114
118
  this.cache = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huaqiu/dsh-auth",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "type": "module",
5
5
  "main": "./lib/index.mjs",
6
6
  "types": "./lib/index.d.mts",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.0",
35
- "@huaqiu/dsh-plugin-log": "0.3.18"
35
+ "@huaqiu/dsh-plugin-log": "0.3.20"
36
36
  },
37
37
  "files": [
38
38
  "lib",
package/src/host.ts CHANGED
@@ -131,15 +131,19 @@ export interface ResolvedHostUser {
131
131
  }
132
132
 
133
133
  /**
134
- * Fetches and caches the host (HQ Edge) session. The cache is memory-only with
135
- * a TTL; the loopback GET is cheap, so we never persist the credential.
134
+ * Fetches the host (HQ Edge) session over the loopback `GET` on EVERY call.
135
+ * The GET is in-memory and loopback-cheap, and the DSH browser half polls this
136
+ * resolver every few seconds (client/index.tsx `refreshHost`) — so re-fetching
137
+ * is exactly what makes a host logout (token cleared) become visible within one
138
+ * poll instead of leaving the UI "logged in" until a page reload.
136
139
  *
137
- * Caching rule — **only a usable session is cached**. A host that is up but has
138
- * no credential yet (EDA not logged in, or the login dialog still open) must
139
- * NOT be cached: the operator may complete the login a second later, and a
140
- * cached negative would pin every tool to `needs_auth` for the whole TTL. The
141
- * old code cached whatever it got, so a DSH that booted before EDA
142
- * authenticated stayed "logged out" for up to `hostSessionTtlSeconds` (300s).
140
+ * Caching rule — **a usable (authenticated) session is NEVER held across a
141
+ * clear**. We fetch fresh each time and drop the cache the moment the host
142
+ * reports no credential (logout / not-yet-logged-in). The only thing the cache
143
+ * is now used for is a TRANSIENT ERROR FALLBACK: if the host is momentarily
144
+ * unreachable we return a recent usable session (within `ttlMs`) rather than
145
+ * spuriously logging the operator out — but never past the TTL, or a stale
146
+ * fallback would mask a real logout that happened while the host was down.
143
147
  */
144
148
  export class HostSessionResolver {
145
149
  private cache: HostSession | null = null
@@ -159,9 +163,6 @@ export class HostSessionResolver {
159
163
  async resolve(): Promise<ResolvedHostUser | null> {
160
164
  if (!this.enabled) return null
161
165
  const now = Date.now()
162
- if (this.cache !== null && now - this.cache.fetchedAt < this.ttlMs) {
163
- return this.cache.info
164
- }
165
166
  const url = `${this.baseUrl}${this.path}`
166
167
  let info: ResolvedHostUser | null = null
167
168
  let status: number | null = null
@@ -176,9 +177,15 @@ export class HostSessionResolver {
176
177
  info = normalizeHostUser(data)
177
178
  }
178
179
  } catch (err) {
179
- // Network error: nothing usable right now.
180
+ // Network error: the host is momentarily unreachable. Fall back to a
181
+ // RECENT usable cache so a transient blip does not log the operator out —
182
+ // but only within the TTL window, or a stale fallback would mask a real
183
+ // logout that happened while the host was down.
180
184
  log().warn('host session fetch failed', { url, error: String(err) })
181
- info = null
185
+ if (this.cache !== null && now - this.cache.fetchedAt < this.ttlMs) {
186
+ return this.cache.info
187
+ }
188
+ return null
182
189
  }
183
190
  if (info !== null && info.authenticated) {
184
191
  this.cache = { info, fetchedAt: now }
@@ -194,7 +201,12 @@ export class HostSessionResolver {
194
201
  return info
195
202
  }
196
203
  // Unusable (unreachable, unparseable, or the host says it has no
197
- // credential). Drop the cache so the very next call re-asks the host.
204
+ // credential — including a logout that just cleared the token). Drop the
205
+ // cache so the very next call re-asks the host and sees the state flip.
206
+ // We NEVER hold a positive (authenticated) session across a clear: the DSH
207
+ // browser half polls every few seconds and would otherwise miss the logout
208
+ // for the whole TTL and stay "logged in" until a reload (see
209
+ // client/index.tsx refreshHost).
198
210
  this.cache = null
199
211
  log().info('no usable host session', {
200
212
  url,