@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 +4 -3
- package/lib/client.js +32 -3
- package/lib/http.js +16 -0
- package/lib/index.js +0 -38
- package/lib/rotate.js +1 -0
- package/lib/routes.js +45 -47
- package/lib/sse.js +1 -0
- package/package.json +5 -5
- package/lib/proactive-refresh.js +0 -37
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
|
-
|
|
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
|
-
|
|
224
|
+
refreshLocks.delete(lockKey)
|
|
224
225
|
}
|
|
225
226
|
})()
|
|
226
|
-
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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,'&').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,'&').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.
|
|
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
|
+
}
|
package/lib/proactive-refresh.js
DELETED
|
@@ -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
|
-
}
|