@goodandready/dsh-key-rotation 0.8.0 → 0.8.1
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/client.js +53 -4
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -240,6 +240,33 @@ window.__ModuleLoader__.load({
|
|
|
240
240
|
// отдельными отмеченными галочками, чтобы правило нельзя было потерять.
|
|
241
241
|
const KNOWN_CODES = ['QUOTA', 'RATE_LIMIT', 'SERVER', 'TIMEOUT', 'TRANSPORT', 'EMPTY_RESPONSE', 'UNKNOWN_MODEL', 'AUTH'];
|
|
242
242
|
|
|
243
|
+
// #273: keep the settings list item mounted if the section throws.
|
|
244
|
+
class KeyRotationErrorBoundary extends React.Component {
|
|
245
|
+
constructor(props) {
|
|
246
|
+
super(props);
|
|
247
|
+
this.state = { error: null };
|
|
248
|
+
}
|
|
249
|
+
static getDerivedStateFromError(error) {
|
|
250
|
+
return { error };
|
|
251
|
+
}
|
|
252
|
+
componentDidCatch(error, info) {
|
|
253
|
+
try { console.error('[dsh-key-rotation] settings card crashed', error, info); } catch (_) {}
|
|
254
|
+
}
|
|
255
|
+
render() {
|
|
256
|
+
if (this.state.error) {
|
|
257
|
+
return React.createElement('div', { className: 'krot krot-err', style: { padding: 12 } },
|
|
258
|
+
React.createElement('p', null, 'Key Rotation settings failed to render.'),
|
|
259
|
+
React.createElement('p', { style: { fontSize: 11, opacity: 0.8 } }, String(this.state.error?.message || this.state.error)),
|
|
260
|
+
React.createElement('button', {
|
|
261
|
+
type: 'button',
|
|
262
|
+
className: 'krot-btn',
|
|
263
|
+
onClick: () => this.setState({ error: null }),
|
|
264
|
+
}, 'Retry'));
|
|
265
|
+
}
|
|
266
|
+
return this.props.children;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
243
270
|
/** Опрос статуса ротации, пока раздел настроек открыт (smart polling). */
|
|
244
271
|
function useRotationStatus() {
|
|
245
272
|
const [byProvider, setByProvider] = React.useState({});
|
|
@@ -414,9 +441,31 @@ window.__ModuleLoader__.load({
|
|
|
414
441
|
}
|
|
415
442
|
return null;
|
|
416
443
|
}, [props.ctx]);
|
|
444
|
+
// #273: getSnapshot must be referentially stable or React 18 unmounts the tree
|
|
445
|
+
const scopeCacheRef = React.useRef({ has: false, value: null });
|
|
446
|
+
const getScopeSnapshot = React.useCallback(() => {
|
|
447
|
+
if (!settingsScope || typeof settingsScope.getSnapshot !== 'function') return null;
|
|
448
|
+
let next = null;
|
|
449
|
+
try {
|
|
450
|
+
next = settingsScope.getSnapshot();
|
|
451
|
+
} catch (_) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const prev = scopeCacheRef.current;
|
|
455
|
+
if (prev.has && Object.is(prev.value, next)) return prev.value;
|
|
456
|
+
// shallow-compare common snapshot shape to avoid identity thrash
|
|
457
|
+
if (prev.has && prev.value && next
|
|
458
|
+
&& prev.value.status === next.status
|
|
459
|
+
&& prev.value.revision === next.revision
|
|
460
|
+
&& prev.value.value === next.value) {
|
|
461
|
+
return prev.value;
|
|
462
|
+
}
|
|
463
|
+
scopeCacheRef.current = { has: true, value: next };
|
|
464
|
+
return next;
|
|
465
|
+
}, [settingsScope]);
|
|
417
466
|
const scopeSnapshot = React.useSyncExternalStore(
|
|
418
|
-
React.useMemo(() => (cb) => (settingsScope ? settingsScope.subscribe(cb) : () => {}), [settingsScope]),
|
|
419
|
-
|
|
467
|
+
React.useMemo(() => (cb) => (settingsScope && typeof settingsScope.subscribe === 'function' ? settingsScope.subscribe(cb) : () => {}), [settingsScope]),
|
|
468
|
+
getScopeSnapshot
|
|
420
469
|
);
|
|
421
470
|
const [state, setState] = React.useState({ status: 'loading', value: null, revision: 0, error: '', providers: [] });
|
|
422
471
|
const [draft, setDraft] = React.useState(null);
|
|
@@ -1185,7 +1234,7 @@ window.__ModuleLoader__.load({
|
|
|
1185
1234
|
h('span', { className: 'krot-card-chevron' + (open ? ' krot-card-chevron-open' : ''), 'aria-hidden': 'true' },
|
|
1186
1235
|
h('svg', { width: 14, height: 14, viewBox: '0 0 14 14', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, style: { display: 'block' } },
|
|
1187
1236
|
h('path', { d: 'M3.5 5.25L7 8.75L10.5 5.25' })))),
|
|
1188
|
-
open ? h('div', { className: 'krot-card-body' }, h(KeyRotationSection, { ...props, locale })) : null);
|
|
1237
|
+
open ? h('div', { className: 'krot-card-body' }, h(KeyRotationErrorBoundary, null, h(KeyRotationSection, { ...props, locale }))) : null);
|
|
1189
1238
|
}
|
|
1190
1239
|
const tryPluginItem = () => {
|
|
1191
1240
|
try {
|
|
@@ -1209,7 +1258,7 @@ window.__ModuleLoader__.load({
|
|
|
1209
1258
|
label: () => (t ? t('title') : 'Key Rotation') || 'Key Rotation',
|
|
1210
1259
|
inject: () => ({ ctx }),
|
|
1211
1260
|
},
|
|
1212
|
-
(props) => h(KeyRotationSection, { ...props, locale: useLocale() }),
|
|
1261
|
+
(props) => h(KeyRotationErrorBoundary, null, h(KeyRotationSection, { ...props, locale: useLocale() })),
|
|
1213
1262
|
));
|
|
1214
1263
|
}
|
|
1215
1264
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-key-rotation",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"packageManager": "pnpm@10.33.2",
|
|
5
5
|
"description": "Per-provider API key rotation for DeepSeek Harness: a key pool per provider, auto-created clone routes, and switching to the next key on quota/rate-limit errors. Includes a Settings section (Key Rotation) to edit the key pools, cooldown and switch codes.",
|
|
6
6
|
"keywords": [
|