@goodandready/dsh-tts 0.4.29 → 0.4.30
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/CHANGELOG.md +13 -0
- package/lib/client.js +63 -33
- package/lib/routes.js +2 -6
- package/lib/updater.js +4 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Supply Chain Security & Updater** (#196, #199):
|
|
7
|
+
- Removed `--config.minimumReleaseAge=0` bypass in `lib/updater.js:installExact`, respecting pnpm supply-chain quarantine during updates.
|
|
8
|
+
- Consolidated updater endpoint to canonical `/api/dsh-tts/update`, removing legacy `/dsh-tts/updater` route alias.
|
|
9
|
+
- Implemented shared `updaterMutex` returning HTTP 409 `Install already in progress` across concurrent requests.
|
|
10
|
+
- **Theme Tokens & Design System** (#197):
|
|
11
|
+
- Replaced legacy CSS custom properties with canonical DSH core theme tokens (`--dsw-state-warn-primary`, `--dsw-brand-primary`, `--dsw-bg-layer-3`).
|
|
12
|
+
- **Client Networking & Timeout Safety** (#198):
|
|
13
|
+
- Wrapped all client `fetch` calls in `clientFetch(url, options)` utility with configurable 15-second `AbortController` timeout.
|
|
14
|
+
- Added error rejection handlers on fire-and-forget telemetry, settings, and player requests.
|
|
15
|
+
|
|
3
16
|
## 0.4.29
|
|
4
17
|
|
|
5
18
|
### Fixed
|
package/lib/client.js
CHANGED
|
@@ -51,7 +51,24 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
function
|
|
54
|
+
function clientFetch(url, options) {
|
|
55
|
+
const opts = options || {}
|
|
56
|
+
const timeoutMs = Number(opts.timeoutMs) > 0 ? Number(opts.timeoutMs) : 15000
|
|
57
|
+
const controller = new AbortController()
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
59
|
+
|
|
60
|
+
if (opts.signal) {
|
|
61
|
+
if (opts.signal.aborted) controller.abort()
|
|
62
|
+
else opts.signal.addEventListener('abort', () => controller.abort(), { once: true })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const fetchOpts = Object.assign({}, opts, { signal: controller.signal })
|
|
66
|
+
delete fetchOpts.timeoutMs
|
|
67
|
+
|
|
68
|
+
return fetch(url, fetchOpts).finally(() => clearTimeout(timer))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getModelHint(provider, t) {
|
|
55
72
|
if (typeof t === 'function') {
|
|
56
73
|
const loc = t('modelHint_' + provider)
|
|
57
74
|
if (loc && !loc.startsWith('modelHint_')) return loc
|
|
@@ -122,12 +139,12 @@ window.__ModuleLoader__.load({
|
|
|
122
139
|
'.dts-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0}' +
|
|
123
140
|
'.dts-field{display:flex;flex-direction:column;gap:6px;padding:6px 0;font-size:13px;color:var(--dsw-alias-label-primary)}' +
|
|
124
141
|
'.dts-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:13px;box-sizing:border-box}' +
|
|
125
|
-
'.dts-input:focus{outline:none;border-color:var(--dsw-alias-
|
|
142
|
+
'.dts-input:focus{outline:none;border-color:var(--dsw-alias-brand-primary, #6366f1)}' +
|
|
126
143
|
'.dts-foot{border-top:1px solid var(--dsw-alias-border-l2);display:flex;justify-content:flex-end;align-items:center;gap:10px;padding:14px 0 4px}' +
|
|
127
144
|
'.dts-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:6px 16px;font-size:13px;font-weight:500;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);transition:all .15s ease}' +
|
|
128
145
|
'.dts-save:hover:not(:disabled){opacity:0.88}' +
|
|
129
146
|
'.dts-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease}' +
|
|
130
|
-
'.dts-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-
|
|
147
|
+
'.dts-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}' +
|
|
131
148
|
'.dts-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent)}' +
|
|
132
149
|
'.dts-btn-danger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 50%, transparent)}' +
|
|
133
150
|
'.dts-entry{display:flex;flex-direction:column;gap:8px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}' +
|
|
@@ -135,18 +152,18 @@ window.__ModuleLoader__.load({
|
|
|
135
152
|
'.dts-row .dts-model{flex:1;min-width:120px}' +
|
|
136
153
|
'.dts-row .dts-key{flex:1;min-width:180px}' +
|
|
137
154
|
'.dts-mini{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:6px;width:28px;height:28px;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;font-size:13px;transition:all .15s ease}' +
|
|
138
|
-
'.dts-mini:hover:not(:disabled){background:var(--dsw-alias-bg-layer-
|
|
155
|
+
'.dts-mini:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2))}' +
|
|
139
156
|
'.dts-ok{font-size:12px;color:var(--dsw-alias-state-success-primary);font-weight:500}' +
|
|
140
157
|
'.dts-bad{font-size:12px;color:var(--dsw-alias-state-error-primary);font-weight:500}' +
|
|
141
158
|
'.dts-badge{font-size:11px;padding:3px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;display:inline-flex;align-items:center;gap:4px;font-weight:500}' +
|
|
142
159
|
'.dts-badge-on{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}' +
|
|
143
|
-
'.dts-badge-warn{color:var(--dsw-alias-state-
|
|
160
|
+
'.dts-badge-warn{color:var(--dsw-alias-state-warn-primary, #f59e0b);border-color:var(--dsw-alias-state-warn-primary, #f59e0b);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f59e0b) 8%, transparent)}' +
|
|
144
161
|
'.dts-badge-bad{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent)}' +
|
|
145
162
|
'.dts-link{background:none;border:none;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:12px;padding:0;text-decoration:underline}' +
|
|
146
163
|
'.dts-link:hover:not(:disabled){color:var(--dsw-alias-label-primary)}' +
|
|
147
164
|
'.dts-alert{padding:10px 14px;border-radius:8px;font-size:13px}' +
|
|
148
165
|
'.dts-alert-err{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary);border:1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 20%, transparent)}' +
|
|
149
|
-
'.dts-alert-warn{background:color-mix(in srgb, var(--dsw-alias-state-
|
|
166
|
+
'.dts-alert-warn{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f59e0b) 10%, transparent);color:var(--dsw-alias-state-warn-primary, #f59e0b);border:1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f59e0b) 20%, transparent)}' +
|
|
150
167
|
'.dts-alert-ok{background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);color:var(--dsw-alias-state-success-primary);border:1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 20%, transparent)}' +
|
|
151
168
|
'.dts-grow{flex:1;min-width:100px}'
|
|
152
169
|
|
|
@@ -543,7 +560,7 @@ window.__ModuleLoader__.load({
|
|
|
543
560
|
|
|
544
561
|
async function pollPending() {
|
|
545
562
|
try {
|
|
546
|
-
const st = await
|
|
563
|
+
const st = await clientFetch('/dsh-tts/status', { cache: 'no-store', timeoutMs: 8000 })
|
|
547
564
|
if (!st.ok) return
|
|
548
565
|
const meta = await st.json()
|
|
549
566
|
player.enabled = !!(meta && meta.speakReplies)
|
|
@@ -552,7 +569,7 @@ window.__ModuleLoader__.load({
|
|
|
552
569
|
if (meta && meta.roles) player.roles = meta.roles
|
|
553
570
|
if (meta && typeof meta.bargeIn === 'boolean') player.bargeIn = meta.bargeIn
|
|
554
571
|
if (!player.enabled) return
|
|
555
|
-
const res = await
|
|
572
|
+
const res = await clientFetch('/dsh-tts/pending?after=' + encodeURIComponent(player.after || ''), { cache: 'no-store', timeoutMs: 8000 })
|
|
556
573
|
if (!res.ok) return
|
|
557
574
|
const data = await res.json()
|
|
558
575
|
const items = data && Array.isArray(data.items) ? data.items : []
|
|
@@ -608,10 +625,11 @@ window.__ModuleLoader__.load({
|
|
|
608
625
|
function playStandalone(text) {
|
|
609
626
|
if (!text) return
|
|
610
627
|
stopPlayback()
|
|
611
|
-
|
|
628
|
+
clientFetch('/dsh-tts/speak', {
|
|
612
629
|
method: 'POST',
|
|
613
630
|
headers: { 'Content-Type': 'application/json' },
|
|
614
631
|
body: JSON.stringify({ text }),
|
|
632
|
+
timeoutMs: 15000,
|
|
615
633
|
})
|
|
616
634
|
.then((r) => r.json())
|
|
617
635
|
.then((d) => {
|
|
@@ -623,15 +641,18 @@ window.__ModuleLoader__.load({
|
|
|
623
641
|
audioBase64: d.audioBase64,
|
|
624
642
|
})
|
|
625
643
|
})
|
|
626
|
-
.catch(() => {
|
|
644
|
+
.catch((err) => {
|
|
645
|
+
console.warn('[dsh-tts] playStandalone fetch failed:', err)
|
|
646
|
+
})
|
|
627
647
|
}
|
|
628
648
|
|
|
629
649
|
function previewSpeech(provider, model, voice, text) {
|
|
630
650
|
stopPlayback()
|
|
631
|
-
return
|
|
651
|
+
return clientFetch('/dsh-tts/preview', {
|
|
632
652
|
method: 'POST',
|
|
633
653
|
headers: { 'Content-Type': 'application/json' },
|
|
634
654
|
body: JSON.stringify({ provider, model, voice, text }),
|
|
655
|
+
timeoutMs: 15000,
|
|
635
656
|
})
|
|
636
657
|
.then((r) => r.json())
|
|
637
658
|
.then((d) => {
|
|
@@ -657,10 +678,11 @@ window.__ModuleLoader__.load({
|
|
|
657
678
|
if (list && list.items && list.items.length) phrase = list.items[0]
|
|
658
679
|
}
|
|
659
680
|
if (!phrase) return
|
|
660
|
-
|
|
681
|
+
clientFetch('/dsh-tts/speak', {
|
|
661
682
|
method: 'POST',
|
|
662
683
|
headers: { 'Content-Type': 'application/json' },
|
|
663
684
|
body: JSON.stringify({ text: phrase }),
|
|
685
|
+
timeoutMs: 15000,
|
|
664
686
|
})
|
|
665
687
|
.then((r) => r.json())
|
|
666
688
|
.then((d) => {
|
|
@@ -1219,8 +1241,8 @@ window.__ModuleLoader__.load({
|
|
|
1219
1241
|
const [models, setModels] = React.useState({})
|
|
1220
1242
|
|
|
1221
1243
|
const loadStatus = React.useCallback(() => {
|
|
1222
|
-
if (typeof fetch === 'undefined') return
|
|
1223
|
-
|
|
1244
|
+
if (typeof clientFetch === 'undefined' && typeof fetch === 'undefined') return
|
|
1245
|
+
clientFetch('/dsh-tts/models/status', { timeoutMs: 8000 })
|
|
1224
1246
|
.then((r) => r.json())
|
|
1225
1247
|
.then((d) => { if (d && d.ok && d.models) setModels(d.models) })
|
|
1226
1248
|
.catch(() => {}) // status poll best-effort
|
|
@@ -1236,11 +1258,14 @@ window.__ModuleLoader__.load({
|
|
|
1236
1258
|
if (typeof window !== 'undefined' && window.confirm) {
|
|
1237
1259
|
if (!window.confirm(t('confirmDeleteModel'))) return
|
|
1238
1260
|
}
|
|
1239
|
-
|
|
1261
|
+
clientFetch('/dsh-tts/models/delete', {
|
|
1240
1262
|
method: 'DELETE',
|
|
1241
1263
|
headers: { 'content-type': 'application/json' },
|
|
1242
1264
|
body: JSON.stringify({ engine }),
|
|
1243
|
-
|
|
1265
|
+
timeoutMs: 10000,
|
|
1266
|
+
}).then(() => loadStatus()).catch((err) => {
|
|
1267
|
+
console.warn('[dsh-tts] model delete failed:', err)
|
|
1268
|
+
})
|
|
1244
1269
|
}
|
|
1245
1270
|
|
|
1246
1271
|
const kokoro = models.kokoro || { installed: false, downloading: false, progress: 0 }
|
|
@@ -1480,7 +1505,7 @@ window.__ModuleLoader__.load({
|
|
|
1480
1505
|
style: {
|
|
1481
1506
|
padding: '10px 14px',
|
|
1482
1507
|
borderRadius: '8px',
|
|
1483
|
-
border: '1px solid var(--dsw-alias-state-
|
|
1508
|
+
border: '1px solid var(--dsw-alias-state-warn-primary, #f59e0b)',
|
|
1484
1509
|
background: 'var(--dsw-alias-bg-layer-2)',
|
|
1485
1510
|
display: 'flex',
|
|
1486
1511
|
flexDirection: 'column',
|
|
@@ -1488,7 +1513,7 @@ window.__ModuleLoader__.load({
|
|
|
1488
1513
|
fontSize: '13px',
|
|
1489
1514
|
},
|
|
1490
1515
|
},
|
|
1491
|
-
React.createElement('span', { style: { fontWeight: 600, color: 'var(--dsw-alias-state-
|
|
1516
|
+
React.createElement('span', { style: { fontWeight: 600, color: 'var(--dsw-alias-state-warn-primary, #f59e0b)' } },
|
|
1492
1517
|
'⚠️ ' + t('voiceNotInstalledTitle')
|
|
1493
1518
|
),
|
|
1494
1519
|
React.createElement('span', { className: 'dts-sub' },
|
|
@@ -1522,7 +1547,7 @@ window.__ModuleLoader__.load({
|
|
|
1522
1547
|
style: {
|
|
1523
1548
|
padding: '10px 14px',
|
|
1524
1549
|
borderRadius: '8px',
|
|
1525
|
-
border: '1px solid var(--dsw-alias-state-
|
|
1550
|
+
border: '1px solid var(--dsw-alias-state-warn-primary, #f59e0b)',
|
|
1526
1551
|
background: 'var(--dsw-alias-bg-layer-2)',
|
|
1527
1552
|
display: 'flex',
|
|
1528
1553
|
flexDirection: 'column',
|
|
@@ -1530,7 +1555,7 @@ window.__ModuleLoader__.load({
|
|
|
1530
1555
|
fontSize: '13px',
|
|
1531
1556
|
},
|
|
1532
1557
|
},
|
|
1533
|
-
React.createElement('span', { style: { fontWeight: 600, color: 'var(--dsw-alias-state-
|
|
1558
|
+
React.createElement('span', { style: { fontWeight: 600, color: 'var(--dsw-alias-state-warn-primary, #f59e0b)' } },
|
|
1534
1559
|
'⚠️ ' + t('messengerNotInstalledTitle')
|
|
1535
1560
|
),
|
|
1536
1561
|
React.createElement('span', { className: 'dts-sub' },
|
|
@@ -1565,8 +1590,9 @@ window.__ModuleLoader__.load({
|
|
|
1565
1590
|
setLoading(true)
|
|
1566
1591
|
setMsg(null)
|
|
1567
1592
|
try {
|
|
1568
|
-
const res = await
|
|
1593
|
+
const res = await clientFetch('/api/dsh-tts/update', {
|
|
1569
1594
|
headers: { 'x-dsh-plugin-update': '1' },
|
|
1595
|
+
timeoutMs: 10000,
|
|
1570
1596
|
})
|
|
1571
1597
|
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
1572
1598
|
const data = await res.json()
|
|
@@ -1582,12 +1608,13 @@ window.__ModuleLoader__.load({
|
|
|
1582
1608
|
setUpdating(true)
|
|
1583
1609
|
setMsg(null)
|
|
1584
1610
|
try {
|
|
1585
|
-
const res = await
|
|
1611
|
+
const res = await clientFetch('/api/dsh-tts/update', {
|
|
1586
1612
|
method: 'POST',
|
|
1587
1613
|
headers: {
|
|
1588
1614
|
'x-dsh-plugin-update': '1',
|
|
1589
1615
|
'content-type': 'application/json',
|
|
1590
1616
|
},
|
|
1617
|
+
timeoutMs: 60000,
|
|
1591
1618
|
})
|
|
1592
1619
|
const data = await res.json()
|
|
1593
1620
|
if (!res.ok) throw new Error((data && (data.error || data.message)) || 'HTTP ' + res.status)
|
|
@@ -1631,7 +1658,7 @@ window.__ModuleLoader__.load({
|
|
|
1631
1658
|
{
|
|
1632
1659
|
type: 'button',
|
|
1633
1660
|
className: 'dts-btn',
|
|
1634
|
-
style: { borderColor: 'var(--dsw-alias-state-
|
|
1661
|
+
style: { borderColor: 'var(--dsw-alias-state-warn-primary, #f59e0b)', color: 'var(--dsw-alias-state-warn-primary, #f59e0b)' },
|
|
1635
1662
|
onClick: onUpdateNow,
|
|
1636
1663
|
disabled: updating || loading,
|
|
1637
1664
|
},
|
|
@@ -1661,11 +1688,11 @@ window.__ModuleLoader__.load({
|
|
|
1661
1688
|
const t = props.t || ((key) => key)
|
|
1662
1689
|
const [data, setData] = React.useState(null)
|
|
1663
1690
|
const loadStats = () => {
|
|
1664
|
-
|
|
1691
|
+
clientFetch('/dsh-tts/stats', { cache: 'no-store', timeoutMs: 8000 }).then((r) => r.json()).then(setData).catch((err) => console.warn('[dsh-tts] loadStats failed:', err))
|
|
1665
1692
|
}
|
|
1666
1693
|
React.useEffect(() => { loadStats() }, [])
|
|
1667
1694
|
const reset = () => {
|
|
1668
|
-
|
|
1695
|
+
clientFetch('/dsh-tts/stats', { method: 'DELETE', timeoutMs: 8000 }).then(loadStats).catch((err) => console.warn('[dsh-tts] resetStats failed:', err))
|
|
1669
1696
|
}
|
|
1670
1697
|
return React.createElement('div', { className: 'dts-block' },
|
|
1671
1698
|
React.createElement('span', { className: 'dts-sub' }, data
|
|
@@ -1751,18 +1778,18 @@ window.__ModuleLoader__.load({
|
|
|
1751
1778
|
|
|
1752
1779
|
React.useEffect(() => {
|
|
1753
1780
|
let alive = true
|
|
1754
|
-
|
|
1781
|
+
clientFetch('/dsh-tts/config', { cache: 'no-store', timeoutMs: 8000 }).then((res) => res.json()).then((data) => {
|
|
1755
1782
|
if (!alive) return
|
|
1756
1783
|
applyPayload(data)
|
|
1757
1784
|
}).catch((e) => { if (alive) setErr(String(e && e.message ? e.message : e)) })
|
|
1758
1785
|
|
|
1759
|
-
|
|
1786
|
+
clientFetch('/dsh-tts/integrations', { cache: 'no-store', timeoutMs: 8000 }).then((res) => res.json()).then((data) => {
|
|
1760
1787
|
if (!alive) return
|
|
1761
1788
|
if (data && data.ok) setIntegrations(data.integrations || data)
|
|
1762
1789
|
}).catch(() => {})
|
|
1763
1790
|
|
|
1764
1791
|
const loadTelemetry = () => {
|
|
1765
|
-
|
|
1792
|
+
clientFetch('/dsh-tts/stats', { cache: 'no-store', timeoutMs: 8000 }).then((res) => res.json()).then((st) => {
|
|
1766
1793
|
if (!alive || !st) return
|
|
1767
1794
|
setTelemetry((prev) => Object.assign({}, prev, {
|
|
1768
1795
|
cacheHits: st.cacheHits || 0,
|
|
@@ -1773,7 +1800,7 @@ window.__ModuleLoader__.load({
|
|
|
1773
1800
|
}).catch(() => {
|
|
1774
1801
|
if (alive) setTelemetry((prev) => Object.assign({}, prev, { online: false }))
|
|
1775
1802
|
})
|
|
1776
|
-
|
|
1803
|
+
clientFetch('/dsh-tts/status', { cache: 'no-store', timeoutMs: 8000 }).then((res) => res.json()).then((st) => {
|
|
1777
1804
|
if (!alive || !st) return
|
|
1778
1805
|
setTelemetry((prev) => Object.assign({}, prev, {
|
|
1779
1806
|
sseClients: st.sseClients || 0,
|
|
@@ -1804,10 +1831,11 @@ window.__ModuleLoader__.load({
|
|
|
1804
1831
|
const value = String(raw != null ? raw : ((keyDrafts && keyDrafts[provider]) || '')).trim()
|
|
1805
1832
|
if (!value) return
|
|
1806
1833
|
setErr('')
|
|
1807
|
-
const res = await
|
|
1834
|
+
const res = await clientFetch('/dsh-tts/credential', {
|
|
1808
1835
|
method: 'PUT',
|
|
1809
1836
|
headers: { 'Content-Type': 'application/json' },
|
|
1810
1837
|
body: JSON.stringify({ provider: provider, value: value }),
|
|
1838
|
+
timeoutMs: 10000,
|
|
1811
1839
|
})
|
|
1812
1840
|
const data = await res.json().catch(() => ({ /* parse error */ }))
|
|
1813
1841
|
if (!res.ok) throw new Error((data && data.error && data.error.message) || ('HTTP ' + res.status))
|
|
@@ -1818,10 +1846,11 @@ window.__ModuleLoader__.load({
|
|
|
1818
1846
|
const clearKey = async (provider) => {
|
|
1819
1847
|
setErr('')
|
|
1820
1848
|
try {
|
|
1821
|
-
const res = await
|
|
1849
|
+
const res = await clientFetch('/dsh-tts/credential', {
|
|
1822
1850
|
method: 'DELETE',
|
|
1823
1851
|
headers: { 'Content-Type': 'application/json' },
|
|
1824
1852
|
body: JSON.stringify({ provider: provider }),
|
|
1853
|
+
timeoutMs: 10000,
|
|
1825
1854
|
})
|
|
1826
1855
|
const data = await res.json().catch(() => ({ /* parse error */ }))
|
|
1827
1856
|
if (!res.ok) throw new Error((data && data.error && data.error.message) || ('HTTP ' + res.status))
|
|
@@ -1843,10 +1872,11 @@ window.__ModuleLoader__.load({
|
|
|
1843
1872
|
if (revision !== null && revision !== undefined) {
|
|
1844
1873
|
bodyPayload.revision = revision
|
|
1845
1874
|
}
|
|
1846
|
-
const res = await
|
|
1875
|
+
const res = await clientFetch('/dsh-tts/config', {
|
|
1847
1876
|
method: 'PUT',
|
|
1848
1877
|
headers: { 'Content-Type': 'application/json' },
|
|
1849
1878
|
body: JSON.stringify(bodyPayload),
|
|
1879
|
+
timeoutMs: 10000,
|
|
1850
1880
|
})
|
|
1851
1881
|
const data = await res.json().catch(() => ({ /* parse error */ }))
|
|
1852
1882
|
if (!res.ok) throw new Error((data && data.error && data.error.message) || ('HTTP ' + res.status))
|
|
@@ -1862,7 +1892,7 @@ window.__ModuleLoader__.load({
|
|
|
1862
1892
|
|
|
1863
1893
|
const clearCacheNow = async () => {
|
|
1864
1894
|
setErr('')
|
|
1865
|
-
const res = await
|
|
1895
|
+
const res = await clientFetch('/dsh-tts/cache', { method: 'DELETE', timeoutMs: 10000 })
|
|
1866
1896
|
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
1867
1897
|
setSaved(true); setTimeout(() => setSaved(false), 2000)
|
|
1868
1898
|
}
|
package/lib/routes.js
CHANGED
|
@@ -495,15 +495,11 @@ export function registerHttpRoutes(ctx, api) {
|
|
|
495
495
|
},
|
|
496
496
|
}), 'dsh-tts: /speak route')
|
|
497
497
|
|
|
498
|
-
|
|
499
|
-
endpoint: '/dsh-tts/updater',
|
|
500
|
-
packageName: '@goodandready/dsh-tts',
|
|
501
|
-
manifestUrl: new URL('../package.json', import.meta.url),
|
|
502
|
-
}), 'dsh-tts: updater route /dsh-tts/updater')
|
|
503
|
-
|
|
498
|
+
const updaterMutex = { installing: false }
|
|
504
499
|
ctx.effect(() => registerPluginUpdater(ctx, {
|
|
505
500
|
endpoint: '/api/dsh-tts/update',
|
|
506
501
|
packageName: '@goodandready/dsh-tts',
|
|
507
502
|
manifestUrl: new URL('../package.json', import.meta.url),
|
|
503
|
+
mutex: updaterMutex,
|
|
508
504
|
}), 'dsh-tts: updater route /api/dsh-tts/update')
|
|
509
505
|
}
|
package/lib/updater.js
CHANGED
|
@@ -225,7 +225,6 @@ async function installExact(target, packageSpec, options) {
|
|
|
225
225
|
'--profile',
|
|
226
226
|
target.profileName,
|
|
227
227
|
'add',
|
|
228
|
-
'--config.minimumReleaseAge=0',
|
|
229
228
|
packageSpec,
|
|
230
229
|
`--registry=${options.registry ?? 'https://registry.npmjs.org/'}`,
|
|
231
230
|
],
|
|
@@ -268,7 +267,7 @@ function json(response, statusCode, value) {
|
|
|
268
267
|
}
|
|
269
268
|
|
|
270
269
|
export function registerPluginUpdater(ctx, options) {
|
|
271
|
-
|
|
270
|
+
const mutex = options?.mutex || { installing: false }
|
|
272
271
|
if (typeof ctx?.webServer?.register !== 'function') {
|
|
273
272
|
ctx?.logger?.warn?.('[dsh-tts] webServer service unavailable, updater skipped')
|
|
274
273
|
return () => {}
|
|
@@ -298,11 +297,11 @@ export function registerPluginUpdater(ctx, options) {
|
|
|
298
297
|
json(response, 403, { error: 'Rejected non-local or cross-origin update request.' })
|
|
299
298
|
return
|
|
300
299
|
}
|
|
301
|
-
if (installing) {
|
|
300
|
+
if (mutex.installing) {
|
|
302
301
|
json(response, 409, { error: 'This plugin is already updating.' })
|
|
303
302
|
return
|
|
304
303
|
}
|
|
305
|
-
installing = true
|
|
304
|
+
mutex.installing = true
|
|
306
305
|
try {
|
|
307
306
|
const before = await status(options, target)
|
|
308
307
|
if (before.latestVersion === undefined) {
|
|
@@ -322,7 +321,7 @@ export function registerPluginUpdater(ctx, options) {
|
|
|
322
321
|
} catch (err) {
|
|
323
322
|
json(response, 503, { error: `Plugin update failed: ${String(err?.message || err)}` })
|
|
324
323
|
} finally {
|
|
325
|
-
installing = false
|
|
324
|
+
mutex.installing = false
|
|
326
325
|
}
|
|
327
326
|
} catch (error) {
|
|
328
327
|
ctx?.logger?.warn?.(`[dsh-tts] plugin updater failed: ${String(error)}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-tts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.30",
|
|
4
4
|
"description": "Text-to-speech for DeepSeek Harness: speak agent replies in the Web UI with a provider fallback chain (OpenAI, ElevenLabs, Google, Azure, Groq, Deepgram, OpenRouter, Edge, Piper, eSpeak).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|