@goodandready/dsh-subscriptions 0.5.30 → 0.5.31

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/accounts.js CHANGED
@@ -189,7 +189,8 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
189
189
  async function ensureFresh(provider, blob, ref) {
190
190
  if (!blob.refreshToken) return blob
191
191
  if (blob.expiresAt && blob.expiresAt - SKEW_MS > Date.now()) return blob
192
- if (ref && refreshLocks.has(ref)) return refreshLocks.get(ref)
192
+ const lockKey = ref || (provider + ":" + (blob.accountId || blob.email || (blob.refreshToken && blob.refreshToken.slice(-16)) || "anon"))
193
+ if (refreshLocks.has(lockKey)) return refreshLocks.get(lockKey)
193
194
  const promise = (async () => {
194
195
  try {
195
196
  const cfg = vendorConfig(provider, getConfig())
@@ -220,10 +221,10 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
220
221
  if (ref) refreshFailures.set(ref, { at: Date.now(), error: String(e && e.message || e) })
221
222
  throw e
222
223
  } finally {
223
- if (ref) refreshLocks.delete(ref)
224
+ refreshLocks.delete(lockKey)
224
225
  }
225
226
  })()
226
- if (ref) refreshLocks.set(ref, promise)
227
+ refreshLocks.set(lockKey, promise)
227
228
  return promise
228
229
  }
229
230
 
package/lib/client.js CHANGED
@@ -12,6 +12,35 @@ window.__ModuleLoader__.load({
12
12
  let t = (key) => key
13
13
  const setT = (fn) => { t = fn }
14
14
 
15
+ class ErrorBoundary extends React.Component {
16
+ constructor(props) {
17
+ super(props)
18
+ this.state = { hasError: false, error: null }
19
+ }
20
+ static getDerivedStateFromError(error) {
21
+ return { hasError: true, error }
22
+ }
23
+ componentDidCatch(error, info) {
24
+ try { console.error("[dsh-subscriptions UI error]", error, info) } catch {}
25
+ }
26
+ render() {
27
+ if (this.state.hasError) {
28
+ return React.createElement(
29
+ "div",
30
+ { style: { padding: "12px", border: "1px solid var(--dsw-alias-state-error-primary, #e5534b)", borderRadius: "8px", background: "var(--dsw-alias-bg-layer-2, #1f1f1f)", margin: "8px 0", fontSize: "13px" } },
31
+ React.createElement("div", { style: { fontWeight: 600, color: "var(--dsw-alias-state-error-primary, #e5534b)", marginBottom: "4px" } }, "Subscriptions UI error"),
32
+ React.createElement("div", { style: { color: "var(--dsw-alias-label-secondary, #8b949e)", fontSize: "12px", marginBottom: "8px" } }, String(this.state.error && this.state.error.message || this.state.error || "Unknown error")),
33
+ React.createElement("button", {
34
+ className: "dsub-mini",
35
+ onClick: () => this.setState({ hasError: false, error: null })
36
+ }, "Retry")
37
+ )
38
+ }
39
+ return this.props.children
40
+ }
41
+ }
42
+
43
+
15
44
  const CSS =
16
45
  '.dsub-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}' +
17
46
  '.dsub-card:hover{border-color:var(--dsw-alias-label-dimmed)}' +
@@ -1322,7 +1351,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1322
1351
  React.createElement(Chevron, { className: 'dsub-chev' + (open ? ' dsub-chevOpen' : '') }),
1323
1352
  ),
1324
1353
  open ? React.createElement('div', { className: 'dsub-body' },
1325
- React.createElement(SubsSection, props),
1354
+ React.createElement(ErrorBoundary, null, React.createElement(SubsSection, props)),
1326
1355
  ) : null,
1327
1356
  )
1328
1357
  }
@@ -1741,7 +1770,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1741
1770
  order: 15,
1742
1771
  locale: NS,
1743
1772
  },
1744
- (props) => React.createElement(SubsPill, { t }),
1773
+ (props) => React.createElement(ErrorBoundary, null, React.createElement(SubsPill, { t })),
1745
1774
  ),
1746
1775
  )
1747
1776
  }, 'dsh-subscriptions: subs pill')
@@ -1757,7 +1786,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1757
1786
  order: 5,
1758
1787
  locale: NS,
1759
1788
  },
1760
- (props) => React.createElement(ComposerQuota, { t }),
1789
+ (props) => React.createElement(ErrorBoundary, null, React.createElement(ComposerQuota, { t })),
1761
1790
  ),
1762
1791
  )
1763
1792
  }, 'dsh-subscriptions: composer quota')
package/lib/http.js CHANGED
@@ -64,3 +64,19 @@ export async function fetchWithTimeout(fetchImpl, url, init = {}, { timeoutMs =
64
64
  clearTimeout(timer)
65
65
  }
66
66
  }
67
+
68
+ export function safeJsonHandler(fn) {
69
+ return async (req, res) => {
70
+ try {
71
+ await fn(req, res)
72
+ } catch (err) {
73
+ const status = Number(err && (err.status || err.statusCode) || 500)
74
+ const code = (err && err.code) || "INTERNAL_ERROR"
75
+ const message = String((err && err.message) || err || "Internal server error")
76
+ writeJson(res, status >= 400 && status < 600 ? status : 500, {
77
+ ok: false,
78
+ error: { code, message },
79
+ })
80
+ }
81
+ }
82
+ }
package/lib/index.js CHANGED
@@ -25,7 +25,6 @@ import { createResetCreditService } from './reset-credits.js'
25
25
  import { maskEmail, maskLabel, maskText } from './mask.js'
26
26
  import { proxyFetch, pickFetch } from './proxy.js'
27
27
  import { HistoryStore } from './history.js'
28
- import { ProactiveTokenRefreshDaemon } from './proactive-refresh.js'
29
28
  import {
30
29
  inspectGoogleAccount,
31
30
  antigravityMetadata,
@@ -112,43 +111,6 @@ export function apply(ctx, config) {
112
111
  const history = new HistoryStore()
113
112
  const recordHistory = (entry) => history.add(entry)
114
113
 
115
- // Proactive token refresh daemon: runs periodically and refreshes tokens nearing expiration
116
- const refreshDaemon = new ProactiveTokenRefreshDaemon({
117
- refreshLeadMs: 15 * 60 * 1000,
118
- checkIntervalMs: 60 * 1000,
119
- })
120
- refreshDaemon.start(
121
- async () => {
122
- const slots = normalizeSlots(live().slots)
123
- const accounts = []
124
- for (const slot of slots) {
125
- try {
126
- const raw = await store.resolveRaw(slot.ref)
127
- if (!raw) continue
128
- const blob = parseBlob(raw)
129
- if (blob && blob.refreshToken && blob.expiresAt) {
130
- accounts.push({
131
- ref: slot.ref,
132
- provider: slot.provider,
133
- refreshToken: blob.refreshToken,
134
- expiresAt: blob.expiresAt,
135
- blob,
136
- })
137
- }
138
- } catch {}
139
- }
140
- return accounts
141
- },
142
- async (acc) => {
143
- try {
144
- await store.ensureFresh(acc.provider, acc.blob, acc.ref)
145
- } catch (e) {
146
- try { ctx.log && ctx.log.warn && ctx.log.warn('[dsh-subscriptions] proactive refresh failed for ' + acc.ref + ': ' + String(e && e.message || e)) } catch {}
147
- }
148
- }
149
- )
150
- ctx.effect(() => () => refreshDaemon.stop(), 'dsh-subscriptions: proactive token refresh daemon')
151
-
152
114
  // #85: host-only reset credit service for Codex accounts.
153
115
  const resetCredits = createResetCreditService({ loadBlob: (ref) => store.loadBlob(ref) })
154
116
  function refForSlot(provider, index) {
package/lib/rotate.js CHANGED
@@ -10,6 +10,7 @@ const SWITCH_CODES = new Set([
10
10
 
11
11
  export function isSwitchableError(err) {
12
12
  if (!err || typeof err !== 'object') return false
13
+ if (err.name === 'AbortError' || err.code === 'ABORT_ERR' || err.code === 'ERR_ABORTED' || (err.message && /aborted/i.test(err.message))) return false
13
14
  const code = err.code || (err.failure && err.failure.code)
14
15
  if (SWITCH_CODES.has(code)) return true
15
16
  const status = err.status || err.statusCode
package/lib/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PROVIDERS, parseOauthRef, displayName, droppedCredentialRefs, oauthRef, isProvider } from './refs.js'
2
- import { writeJson, writeHtml, readBody, isTrustedSettingsRequest, queryOf } from './http.js'
2
+ import { writeJson, writeHtml, readBody, isTrustedSettingsRequest, queryOf, safeJsonHandler } from './http.js'
3
3
  import { parseCallbackInput, requestOrigin, webCallbackUri } from './oauth.js'
4
4
  import { createPkce } from './pkce.js'
5
5
  import { startLoopback } from './loopback.js'
@@ -97,7 +97,7 @@ export function registerRoutes(ctx, state) {
97
97
  ctx.effect(() => ctx.webServer.register({
98
98
  kind: 'exact',
99
99
  path: '/dsh-subscriptions/status',
100
- handler: async (req, res) => {
100
+ handler: safeJsonHandler(async (req, res) => {
101
101
  if (req.method !== 'GET') {
102
102
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
103
103
  return
@@ -165,13 +165,13 @@ export function registerRoutes(ctx, state) {
165
165
  composerQuota: String(live().composerQuota || 'off'),
166
166
  active,
167
167
  })
168
- },
168
+ }),
169
169
  }), 'dsh-subscriptions: /status')
170
170
 
171
171
  ctx.effect(() => ctx.webServer.register({
172
172
  kind: 'exact',
173
173
  path: '/dsh-subscriptions/reset-credits',
174
- handler: async (req, res) => {
174
+ handler: safeJsonHandler(async (req, res) => {
175
175
  if (req.method !== 'GET') {
176
176
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
177
177
  return
@@ -186,13 +186,13 @@ export function registerRoutes(ctx, state) {
186
186
  } catch (e) {
187
187
  writeJson(res, 200, { ok: false, error: { code: 'reset', message: String(e && e.message || e) } })
188
188
  }
189
- },
189
+ }),
190
190
  }), 'dsh-subscriptions: /reset-credits')
191
191
 
192
192
  ctx.effect(() => ctx.webServer.register({
193
193
  kind: 'exact',
194
194
  path: '/dsh-subscriptions/reset-credits/prepare',
195
- handler: async (req, res) => {
195
+ handler: safeJsonHandler(async (req, res) => {
196
196
  if (req.method !== 'POST') {
197
197
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
198
198
  return
@@ -213,13 +213,13 @@ export function registerRoutes(ctx, state) {
213
213
  } catch (e) {
214
214
  writeJson(res, 200, { ok: false, error: { code: 'reset', message: String(e && e.message || e) } })
215
215
  }
216
- },
216
+ }),
217
217
  }), 'dsh-subscriptions: /reset-credits/prepare')
218
218
 
219
219
  ctx.effect(() => ctx.webServer.register({
220
220
  kind: 'exact',
221
221
  path: '/dsh-subscriptions/reset-credits/consume',
222
- handler: async (req, res) => {
222
+ handler: safeJsonHandler(async (req, res) => {
223
223
  if (req.method !== 'POST') {
224
224
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
225
225
  return
@@ -240,25 +240,25 @@ export function registerRoutes(ctx, state) {
240
240
  } catch (e) {
241
241
  writeJson(res, 200, { ok: false, error: { code: 'reset', message: String(e && e.message || e) } })
242
242
  }
243
- },
243
+ }),
244
244
  }), 'dsh-subscriptions: /reset-credits/consume')
245
245
 
246
246
  ctx.effect(() => ctx.webServer.register({
247
247
  kind: 'exact',
248
248
  path: '/dsh-subscriptions/diagnostics',
249
- handler: async (req, res) => {
249
+ handler: safeJsonHandler(async (req, res) => {
250
250
  if (req.method !== 'GET') {
251
251
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
252
252
  return
253
253
  }
254
254
  writeJson(res, 200, { ok: true, report: await diagnosticsReport() })
255
- },
255
+ }),
256
256
  }), 'dsh-subscriptions: /diagnostics')
257
257
 
258
258
  ctx.effect(() => ctx.webServer.register({
259
259
  kind: 'exact',
260
260
  path: '/dsh-subscriptions/oauth/start',
261
- handler: async (req, res) => {
261
+ handler: safeJsonHandler(async (req, res) => {
262
262
  if (req.method !== 'GET') {
263
263
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
264
264
  return
@@ -305,7 +305,7 @@ export function registerRoutes(ctx, state) {
305
305
  } catch { autoCatch = false }
306
306
  }
307
307
  writeJson(res, 200, { ok: true, url, state: pkce.state, redirectUri, autoCatch })
308
- },
308
+ }),
309
309
  }), 'dsh-subscriptions: /oauth/start')
310
310
 
311
311
  // #90: Device-code login (headless/VPS). Codex-only: auth.openai.com mints an
@@ -315,7 +315,7 @@ export function registerRoutes(ctx, state) {
315
315
  ctx.effect(() => ctx.webServer.register({
316
316
  kind: 'exact',
317
317
  path: '/dsh-subscriptions/oauth/device/start',
318
- handler: async (req, res) => {
318
+ handler: safeJsonHandler(async (req, res) => {
319
319
  if (req.method !== 'POST') {
320
320
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
321
321
  return
@@ -361,13 +361,13 @@ export function registerRoutes(ctx, state) {
361
361
  } catch (e) {
362
362
  writeJson(res, 502, { ok: false, error: { code: e && e.code || 'DEVICE', message: String(e && e.message || e).slice(0, 300) } })
363
363
  }
364
- },
364
+ }),
365
365
  }), 'dsh-subscriptions: /oauth/device/start')
366
366
 
367
367
  ctx.effect(() => ctx.webServer.register({
368
368
  kind: 'exact',
369
369
  path: '/dsh-subscriptions/oauth/device/poll',
370
- handler: async (req, res) => {
370
+ handler: safeJsonHandler(async (req, res) => {
371
371
  if (req.method !== 'POST') {
372
372
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
373
373
  return
@@ -410,7 +410,7 @@ export function registerRoutes(ctx, state) {
410
410
  } catch (e) {
411
411
  writeJson(res, 502, { ok: false, error: { code: e && e.code || 'DEVICE', message: String(e && e.message || e).slice(0, 300) } })
412
412
  }
413
- },
413
+ }),
414
414
  }), 'dsh-subscriptions: /oauth/device/poll')
415
415
 
416
416
  ctx.effect(() => ctx.webServer.register({
@@ -441,7 +441,7 @@ export function registerRoutes(ctx, state) {
441
441
  ctx.effect(() => ctx.webServer.register({
442
442
  kind: 'exact',
443
443
  path: '/dsh-subscriptions/oauth/complete',
444
- handler: async (req, res) => {
444
+ handler: safeJsonHandler(async (req, res) => {
445
445
  if (req.method !== 'POST') {
446
446
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
447
447
  return
@@ -470,14 +470,14 @@ export function registerRoutes(ctx, state) {
470
470
  } catch (e) {
471
471
  writeJson(res, 400, { ok: false, error: { code: 'oauth', message: String(e && e.message || e) } })
472
472
  }
473
- },
473
+ }),
474
474
  }), 'dsh-subscriptions: /oauth/complete')
475
475
 
476
476
  // ponytail: cheap per-vendor probe; never sets cooldown, never returns tokens
477
477
  ctx.effect(() => ctx.webServer.register({
478
478
  kind: 'exact',
479
479
  path: '/dsh-subscriptions/check',
480
- handler: async (req, res) => {
480
+ handler: safeJsonHandler(async (req, res) => {
481
481
  if (req.method !== 'POST') {
482
482
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
483
483
  return
@@ -541,13 +541,13 @@ export function registerRoutes(ctx, state) {
541
541
  error: { code: e && e.code ? e.code : 'VENDOR', message: String(e && e.message || e).slice(0, 300) },
542
542
  })
543
543
  }
544
- },
544
+ }),
545
545
  }), 'dsh-subscriptions: /check')
546
546
 
547
547
  ctx.effect(() => ctx.webServer.register({
548
548
  kind: 'exact',
549
549
  path: '/dsh-subscriptions/discover-local',
550
- handler: async (req, res) => {
550
+ handler: safeJsonHandler(async (req, res) => {
551
551
  if (req.method !== 'GET') {
552
552
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET only' } })
553
553
  return
@@ -558,13 +558,13 @@ export function registerRoutes(ctx, state) {
558
558
  } catch (e) {
559
559
  writeJson(res, 500, { ok: false, error: { message: String(e && e.message || e) } })
560
560
  }
561
- },
561
+ }),
562
562
  }), 'dsh-subscriptions: /discover-local')
563
563
 
564
564
  ctx.effect(() => ctx.webServer.register({
565
565
  kind: 'exact',
566
566
  path: '/dsh-subscriptions/import-local',
567
- handler: async (req, res) => {
567
+ handler: safeJsonHandler(async (req, res) => {
568
568
  if (req.method !== 'POST') {
569
569
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
570
570
  return
@@ -615,26 +615,24 @@ export function registerRoutes(ctx, state) {
615
615
  } catch (e) {
616
616
  writeJson(res, 400, { ok: false, error: { message: String(e && e.message || e) } })
617
617
  }
618
- },
618
+ }),
619
619
  }), 'dsh-subscriptions: /import-local')
620
620
 
621
621
  ctx.effect(() => ctx.webServer.register({
622
622
  kind: 'exact',
623
623
  path: '/dsh-subscriptions/analyze-session',
624
- handler: async (req, res) => {
624
+ handler: safeJsonHandler(async (req, res) => {
625
625
  if (req.method !== 'POST') {
626
626
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
627
627
  return
628
628
  }
629
- try {
630
- const body = await readBody(req).catch(() => ({}))
631
- const events = Array.isArray(body && body.events) ? body.events : []
632
- const analysis = analyzeSessionEvents(events)
633
- writeJson(res, 200, { ok: true, analysis })
634
- } catch (e) {
635
- writeJson(res, 500, { ok: false, error: { message: String(e && e.message || e) } })
636
- }
637
- },
629
+ const raw = await readBody(req, 128 * 1024).catch(() => Buffer.alloc(0))
630
+ let body = {}
631
+ try { body = JSON.parse(raw.toString('utf8') || '{}') } catch {}
632
+ const events = Array.isArray(body && body.events) ? body.events : []
633
+ const analysis = analyzeSessionEvents(events)
634
+ writeJson(res, 200, { ok: true, analysis })
635
+ }),
638
636
  }), 'dsh-subscriptions: /analyze-session')
639
637
 
640
638
 
@@ -645,7 +643,7 @@ export function registerRoutes(ctx, state) {
645
643
  ctx.effect(() => ctx.webServer.register({
646
644
  kind: 'prefix',
647
645
  path: '/dsh-subscriptions/proxy',
648
- handler: async (req, res) => {
646
+ handler: safeJsonHandler(async (req, res) => {
649
647
  if (req.method !== 'POST' && req.method !== 'GET') {
650
648
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'GET or POST' } })
651
649
  return
@@ -691,14 +689,14 @@ export function registerRoutes(ctx, state) {
691
689
  const status = e && e.status ? e.status : (e && e.code === 'FORBIDDEN' ? 403 : (e && e.code === 'AUTH' ? 401 : 502))
692
690
  writeJson(res, status, { ok: false, error: { code: e && e.code || 'VENDOR', message: String(e && e.message || e).slice(0, 300) } })
693
691
  }
694
- },
692
+ }),
695
693
  }), 'dsh-subscriptions: proxy')
696
694
 
697
695
  // #88: проверка прокси аккаунта — реальный запрос к эндпоинту провайдера с замером задержки.
698
696
  ctx.effect(() => ctx.webServer.register({
699
697
  kind: 'exact',
700
698
  path: '/dsh-subscriptions/proxy-check',
701
- handler: async (req, res) => {
699
+ handler: safeJsonHandler(async (req, res) => {
702
700
  if (req.method !== 'POST') {
703
701
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
704
702
  return
@@ -749,14 +747,14 @@ export function registerRoutes(ctx, state) {
749
747
  error: { code: (e && e.code) || 'NETWORK', message: String((e && e.message) || e).slice(0, 200) },
750
748
  })
751
749
  }
752
- },
750
+ }),
753
751
  }), 'dsh-subscriptions: proxy-check')
754
752
 
755
753
  // Экспорт зашифрованного бандла токенов. Токены не логгируются.
756
754
  ctx.effect(() => ctx.webServer.register({
757
755
  kind: 'exact',
758
756
  path: '/dsh-subscriptions/export',
759
- handler: async (req, res) => {
757
+ handler: safeJsonHandler(async (req, res) => {
760
758
  if (req.method !== 'POST') {
761
759
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
762
760
  return
@@ -793,14 +791,14 @@ export function registerRoutes(ctx, state) {
793
791
  } catch (e) {
794
792
  writeJson(res, 500, { ok: false, error: { code: 'export', message: String(e && e.message || e) } })
795
793
  }
796
- },
794
+ }),
797
795
  }), 'dsh-subscriptions: /export')
798
796
 
799
797
  // Импорт зашифрованного бандла.
800
798
  ctx.effect(() => ctx.webServer.register({
801
799
  kind: 'exact',
802
800
  path: '/dsh-subscriptions/import',
803
- handler: async (req, res) => {
801
+ handler: safeJsonHandler(async (req, res) => {
804
802
  if (req.method !== 'POST') {
805
803
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
806
804
  return
@@ -839,14 +837,14 @@ export function registerRoutes(ctx, state) {
839
837
  }
840
838
  await syncAdapter()
841
839
  writeJson(res, 200, { ok: true, imported, total: bundle.accounts.length, accounts: await accountsView() })
842
- },
840
+ }),
843
841
  }), 'dsh-subscriptions: /import')
844
842
 
845
843
  // #45: импорт существующего refresh token / API key без OAuth-флоу.
846
844
  ctx.effect(() => ctx.webServer.register({
847
845
  kind: 'exact',
848
846
  path: '/dsh-subscriptions/import-token',
849
- handler: async (req, res) => {
847
+ handler: safeJsonHandler(async (req, res) => {
850
848
  if (req.method !== 'POST') {
851
849
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
852
850
  return
@@ -883,7 +881,7 @@ export function registerRoutes(ctx, state) {
883
881
  } catch (e) {
884
882
  writeJson(res, 400, { ok: false, error: { code: 'import', message: String(e && e.message || e) } })
885
883
  }
886
- },
884
+ }),
887
885
  }), 'dsh-subscriptions: /import-token')
888
886
 
889
887
  // #50: сводная страница /subscriptions (localhost-only).
@@ -981,7 +979,7 @@ function esc(x){return String(x==null?'':x).replace(/&/g,'&amp;').replace(/</g,'
981
979
  ctx.effect(() => ctx.webServer.register({
982
980
  kind: 'exact',
983
981
  path: '/dsh-subscriptions/logout',
984
- handler: async (req, res) => {
982
+ handler: safeJsonHandler(async (req, res) => {
985
983
  if (req.method !== 'POST') {
986
984
  writeJson(res, 405, { ok: false, error: { code: 'method', message: 'POST only' } })
987
985
  return
@@ -1004,7 +1002,7 @@ function esc(x){return String(x==null?'':x).replace(/&/g,'&amp;').replace(/</g,'
1004
1002
  } catch (e) {
1005
1003
  writeJson(res, 400, { ok: false, error: { code: 'logout', message: String(e && e.message || e) } })
1006
1004
  }
1007
- },
1005
+ }),
1008
1006
  }), 'dsh-subscriptions: /logout')
1009
1007
  }
1010
1008
 
package/lib/sse.js CHANGED
@@ -10,6 +10,7 @@ export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {})
10
10
  buffer = buffer.slice(sep).replace(/^\r?\n\r?\n/, '')
11
11
  const dataLines = []
12
12
  for (const line of raw.split(/\r?\n/)) {
13
+ if (line.startsWith(":")) continue // SSE comments / keep-alive pings
13
14
  if (line.startsWith('data:')) dataLines.push(line.slice(5).trimStart())
14
15
  }
15
16
  if (!dataLines.length) continue
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.5.30",
3
+ "version": "0.5.31",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, and Antigravity subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,9 +34,6 @@
34
34
  "bugs": {
35
35
  "url": "https://github.com/GooDAnDReaDY/dsh-subscriptions/issues"
36
36
  },
37
- "scripts": {
38
- "test": "eslint lib/ && node --test test/*.test.mjs"
39
- },
40
37
  "dsh": {
41
38
  "bundle": {
42
39
  "patch": "./cordis.patch.yml"
@@ -63,5 +60,8 @@
63
60
  },
64
61
  "devDependencies": {
65
62
  "@deepseek-ai/dsh-llm": "0.1.0-rc.8"
63
+ },
64
+ "scripts": {
65
+ "test": "eslint lib/ && node --test test/*.test.mjs"
66
66
  }
67
- }
67
+ }
@@ -1,37 +0,0 @@
1
- export class ProactiveTokenRefreshDaemon {
2
- constructor({ refreshLeadMs = 15 * 60 * 1000, checkIntervalMs = 60 * 1000 } = {}) {
3
- this.refreshLeadMs = refreshLeadMs // refresh 15 min before token expires
4
- this.checkIntervalMs = checkIntervalMs
5
- this.timer = null
6
- }
7
-
8
- start(getAccountsFn, refreshFn) {
9
- if (this.timer) return
10
- this.timer = setInterval(async () => {
11
- try {
12
- const accounts = (typeof getAccountsFn === 'function' && await getAccountsFn()) || []
13
- const now = Date.now()
14
- for (const acc of accounts) {
15
- if (!acc || !acc.expiresAt || !acc.refreshToken) continue
16
- const expiresAt = Number(acc.expiresAt)
17
- if (expiresAt - now <= this.refreshLeadMs && expiresAt > now) {
18
- if (typeof refreshFn === 'function') {
19
- await refreshFn(acc)
20
- }
21
- }
22
- }
23
- } catch {
24
- // silent catch on daemon tick
25
- }
26
- }, this.checkIntervalMs)
27
-
28
- if (this.timer.unref) this.timer.unref()
29
- }
30
-
31
- stop() {
32
- if (this.timer) {
33
- clearInterval(this.timer)
34
- this.timer = null
35
- }
36
- }
37
- }