dsh-plugin-subscriptions 0.5.1 → 0.5.3
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/README.md +42 -1
- package/README.zh.md +42 -1
- package/lib/auth/device-flow.d.ts +0 -9
- package/lib/auth/device-flow.js +2 -1
- package/lib/auth/rpc.d.ts +44 -13
- package/lib/auth/rpc.js +127 -9
- package/lib/auth/store.d.ts +75 -17
- package/lib/auth/store.js +148 -27
- package/lib/client/SubscriptionsSection.d.ts +26 -3
- package/lib/client/SubscriptionsSection.js +263 -67
- package/lib/client/index.js +11 -0
- package/lib/client/locales.d.ts +82 -10
- package/lib/client/locales.js +82 -10
- package/lib/client.js +837 -223
- package/lib/client.js.map +1 -1
- package/lib/http.d.ts +114 -0
- package/lib/http.js +402 -0
- package/lib/index.d.ts +21 -0
- package/lib/index.js +1938 -208
- package/lib/providers/accounts.d.ts +102 -0
- package/lib/providers/accounts.js +123 -0
- package/lib/providers/antigravity.d.ts +90 -0
- package/lib/providers/antigravity.js +392 -0
- package/lib/providers/claude.d.ts +22 -4
- package/lib/providers/claude.js +97 -16
- package/lib/providers/codex.d.ts +24 -3
- package/lib/providers/codex.js +121 -21
- package/lib/providers/common.d.ts +17 -0
- package/lib/providers/common.js +67 -3
- package/lib/providers/copilot.d.ts +23 -4
- package/lib/providers/copilot.js +99 -19
- package/lib/providers/grok.d.ts +24 -4
- package/lib/providers/grok.js +106 -19
- package/lib/providers/pool-family.d.ts +56 -0
- package/lib/providers/pool-family.js +45 -0
- package/lib/providers/pool-health.d.ts +74 -0
- package/lib/providers/pool-health.js +148 -0
- package/lib/providers/pool-usage.d.ts +57 -0
- package/lib/providers/pool-usage.js +130 -0
- package/lib/providers/pool.d.ts +107 -0
- package/lib/providers/pool.js +371 -0
- package/lib/tools/image-generate.d.ts +3 -3
- package/lib/tools/image-generate.js +4 -2
- package/lib/tools/video-generate.d.ts +2 -2
- package/lib/tools/video-generate.js +4 -2
- package/lib/tools/x-search.d.ts +2 -2
- package/lib/tools/x-search.js +4 -2
- package/lib/translate/antigravity.d.ts +110 -0
- package/lib/translate/antigravity.js +303 -0
- package/package.json +14 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Subscriptions settings section: one card per subscription provider with an
|
|
4
4
|
* OAuth login/logout flow driven by the node half's `/subscriptions-auth` RPC
|
|
@@ -54,6 +54,16 @@ export async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
|
54
54
|
function messageOf(error) {
|
|
55
55
|
return error instanceof Error ? error.message : String(error);
|
|
56
56
|
}
|
|
57
|
+
/** Copy a keyed map without the entries whose key is not in `live`. */
|
|
58
|
+
function dropStale(map, live) {
|
|
59
|
+
const stale = Object.keys(map).filter(key => !live.has(key));
|
|
60
|
+
if (stale.length === 0)
|
|
61
|
+
return map;
|
|
62
|
+
const next = { ...map };
|
|
63
|
+
for (const key of stale)
|
|
64
|
+
delete next[key];
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
57
67
|
/**
|
|
58
68
|
* English-dictionary fallback for a missing inject `t` (standalone renders);
|
|
59
69
|
* the slot inject always supplies the locale-bound one.
|
|
@@ -78,6 +88,10 @@ const styles = {
|
|
|
78
88
|
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12,
|
|
79
89
|
padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
|
|
80
90
|
},
|
|
91
|
+
proxyCard: {
|
|
92
|
+
padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
|
|
93
|
+
},
|
|
94
|
+
separator: { borderTop: '1px solid var(--dsw-alias-border-l2)' },
|
|
81
95
|
cardHeader: { display: 'flex', alignItems: 'center', gap: 8 },
|
|
82
96
|
dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },
|
|
83
97
|
name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
|
|
@@ -110,6 +124,18 @@ const styles = {
|
|
|
110
124
|
display: 'flex', justifyContent: 'space-between', gap: 8,
|
|
111
125
|
fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
|
|
112
126
|
},
|
|
127
|
+
accountRow: {
|
|
128
|
+
display: 'flex', flexDirection: 'column', gap: 6,
|
|
129
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
130
|
+
padding: '8px 10px', marginTop: 4,
|
|
131
|
+
},
|
|
132
|
+
accountHeader: { display: 'flex', alignItems: 'center', gap: 8 },
|
|
133
|
+
accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },
|
|
134
|
+
starButton: {
|
|
135
|
+
border: 'none', background: 'transparent', padding: 0,
|
|
136
|
+
font: 'inherit', fontSize: 14, lineHeight: '20px', cursor: 'pointer',
|
|
137
|
+
color: 'var(--dsw-alias-state-warn-label)',
|
|
138
|
+
},
|
|
113
139
|
usageTrack: {
|
|
114
140
|
height: 6, borderRadius: 3, overflow: 'hidden',
|
|
115
141
|
background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
@@ -132,12 +158,42 @@ const styles = {
|
|
|
132
158
|
fontFamily: 'monospace', fontSize: 18, lineHeight: '24px', letterSpacing: 2,
|
|
133
159
|
color: 'var(--dsw-alias-label-primary)', userSelect: 'all',
|
|
134
160
|
},
|
|
161
|
+
proxyField: { display: 'flex', flexDirection: 'column', gap: 4 },
|
|
162
|
+
proxyLabel: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' },
|
|
163
|
+
proxyInput: {
|
|
164
|
+
height: 32, width: '100%', boxSizing: 'border-box',
|
|
165
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
166
|
+
padding: '0 10px', font: 'inherit', fontSize: 14, lineHeight: '22px',
|
|
167
|
+
background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
|
|
168
|
+
},
|
|
169
|
+
proxyHint: {
|
|
170
|
+
margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
|
|
171
|
+
},
|
|
172
|
+
proxyCheck: {
|
|
173
|
+
display: 'flex', alignItems: 'center', gap: 8,
|
|
174
|
+
fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', cursor: 'pointer',
|
|
175
|
+
},
|
|
176
|
+
proxyMessage: { margin: 0, fontSize: 12, lineHeight: '18px' },
|
|
177
|
+
proxyActions: { display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'flex-end', marginTop: 2 },
|
|
178
|
+
modalOverlay: {
|
|
179
|
+
position: 'fixed', inset: 0, zIndex: 1000,
|
|
180
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16,
|
|
181
|
+
background: 'rgba(0, 0, 0, 0.45)',
|
|
182
|
+
},
|
|
183
|
+
modal: {
|
|
184
|
+
width: 460, maxWidth: '100%', maxHeight: '90vh', overflowY: 'auto',
|
|
185
|
+
boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 12,
|
|
186
|
+
padding: '16px 18px', borderRadius: 12,
|
|
187
|
+
background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
188
|
+
},
|
|
189
|
+
modalHeader: { display: 'flex', alignItems: 'center', gap: 8 },
|
|
190
|
+
modalTitle: { fontWeight: 600, fontSize: 15, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
|
|
135
191
|
};
|
|
136
192
|
/** Status dot color for one provider state. */
|
|
137
193
|
function dotColor(status) {
|
|
138
194
|
if (status?.busy === true)
|
|
139
195
|
return 'var(--dsw-alias-state-warn-label)';
|
|
140
|
-
if (status?.
|
|
196
|
+
if ((status?.accounts.length ?? 0) > 0)
|
|
141
197
|
return 'var(--dsw-alias-state-success-primary)';
|
|
142
198
|
return 'var(--dsw-alias-label-dimmed)';
|
|
143
199
|
}
|
|
@@ -152,20 +208,8 @@ function statusText(t, status) {
|
|
|
152
208
|
return t('checking');
|
|
153
209
|
if (status.busy)
|
|
154
210
|
return t('loginInProgress');
|
|
155
|
-
if (status.
|
|
156
|
-
|
|
157
|
-
if (status.account !== undefined)
|
|
158
|
-
params.account = status.account;
|
|
159
|
-
if (status.expiresAt !== undefined)
|
|
160
|
-
params.date = new Date(status.expiresAt).toLocaleString();
|
|
161
|
-
if (params.account !== undefined && params.date !== undefined)
|
|
162
|
-
return t('loggedInAccountExpires', params);
|
|
163
|
-
if (params.account !== undefined)
|
|
164
|
-
return t('loggedInAccount', params);
|
|
165
|
-
if (params.date !== undefined)
|
|
166
|
-
return t('loggedInExpires', params);
|
|
167
|
-
return t('loggedIn');
|
|
168
|
-
}
|
|
211
|
+
if (status.accounts.length > 0)
|
|
212
|
+
return t('loggedInCount', { count: status.accounts.length });
|
|
169
213
|
return t('notLoggedIn');
|
|
170
214
|
}
|
|
171
215
|
/**
|
|
@@ -188,6 +232,24 @@ function usageBarColor(usedPercent) {
|
|
|
188
232
|
return 'var(--dsw-alias-state-warn-label)';
|
|
189
233
|
return 'var(--dsw-alias-state-success-primary)';
|
|
190
234
|
}
|
|
235
|
+
/** One-line status text of the proxy config card. */
|
|
236
|
+
function proxyStatusText(t, proxy, loadError) {
|
|
237
|
+
if (loadError !== undefined)
|
|
238
|
+
return t('proxyLoadFailed', { message: loadError });
|
|
239
|
+
if (proxy === undefined)
|
|
240
|
+
return t('proxyLoading');
|
|
241
|
+
if (proxy.error !== undefined)
|
|
242
|
+
return t('proxyStatusError', { message: proxy.error });
|
|
243
|
+
if (proxy.enabled)
|
|
244
|
+
return t('proxyStatusEnabled', { url: proxy.url });
|
|
245
|
+
return t('proxyStatusNone');
|
|
246
|
+
}
|
|
247
|
+
/** Feedback-line color of the proxy dialog. */
|
|
248
|
+
function messageColor(tone) {
|
|
249
|
+
return tone === 'error'
|
|
250
|
+
? 'var(--dsw-alias-state-error-primary)'
|
|
251
|
+
: 'var(--dsw-alias-state-success-primary)';
|
|
252
|
+
}
|
|
191
253
|
/**
|
|
192
254
|
* The Subscriptions settings page component.
|
|
193
255
|
* @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
|
|
@@ -204,13 +266,29 @@ export function SubscriptionsSection(props) {
|
|
|
204
266
|
/** Pending device-flow codes (copilot), shown while the attempt polls. */
|
|
205
267
|
const [deviceCodes, setDeviceCodes] = useState({});
|
|
206
268
|
const [copiedCode, setCopiedCode] = useState(undefined);
|
|
269
|
+
/** Usage snapshots keyed `${provider}:${accountKey}` — every account tracks its own windows. */
|
|
207
270
|
const [usages, setUsages] = useState({});
|
|
208
271
|
const [usageErrors, setUsageErrors] = useState({});
|
|
209
272
|
const [usageLoading, setUsageLoading] = useState({});
|
|
210
273
|
const mountedRef = useRef(true);
|
|
211
274
|
const pollersRef = useRef(new Map());
|
|
212
|
-
/**
|
|
275
|
+
/** Accounts with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
|
|
213
276
|
const usageInflightRef = useRef(new Set());
|
|
277
|
+
/** Proxy config as last answered by `proxyGet`/`proxySet`. */
|
|
278
|
+
const [proxy, setProxy] = useState(undefined);
|
|
279
|
+
const [proxyLoadError, setProxyLoadError] = useState(undefined);
|
|
280
|
+
/** Proxy dialog state (draft fields; the password never pre-fills). */
|
|
281
|
+
const [proxyOpen, setProxyOpen] = useState(false);
|
|
282
|
+
const [proxyEnabled, setProxyEnabled] = useState(false);
|
|
283
|
+
const [proxyUrl, setProxyUrl] = useState('');
|
|
284
|
+
const [proxyUsername, setProxyUsername] = useState('');
|
|
285
|
+
const [proxyPassword, setProxyPassword] = useState('');
|
|
286
|
+
const [proxyClearPassword, setProxyClearPassword] = useState(false);
|
|
287
|
+
const [proxyBypass, setProxyBypass] = useState('');
|
|
288
|
+
const [proxySaving, setProxySaving] = useState(false);
|
|
289
|
+
const [proxyTesting, setProxyTesting] = useState(false);
|
|
290
|
+
const [proxyMessage, setProxyMessage] = useState(undefined);
|
|
291
|
+
const [proxyTestResult, setProxyTestResult] = useState(undefined);
|
|
214
292
|
const setProviderError = useCallback((provider, message) => {
|
|
215
293
|
if (!mountedRef.current)
|
|
216
294
|
return;
|
|
@@ -248,7 +326,7 @@ export function SubscriptionsSection(props) {
|
|
|
248
326
|
setStatuses(response.providers);
|
|
249
327
|
for (const { id } of PROVIDERS) {
|
|
250
328
|
const status = response.providers[id];
|
|
251
|
-
if (status.
|
|
329
|
+
if (status.accounts.length > 0 || !status.busy) {
|
|
252
330
|
stopPolling(id);
|
|
253
331
|
// The attempt settled (success, timeout, or cancel): drop the code card.
|
|
254
332
|
setDeviceCodes((prev) => {
|
|
@@ -288,64 +366,58 @@ export function SubscriptionsSection(props) {
|
|
|
288
366
|
pollersRef.current.clear();
|
|
289
367
|
};
|
|
290
368
|
}, [refresh, startPolling]);
|
|
291
|
-
const loadUsage = useCallback(async (provider) => {
|
|
292
|
-
|
|
369
|
+
const loadUsage = useCallback(async (provider, account) => {
|
|
370
|
+
const key = `${provider}:${account}`;
|
|
371
|
+
if (rpc === undefined || usageInflightRef.current.has(key))
|
|
293
372
|
return;
|
|
294
|
-
usageInflightRef.current.add(
|
|
295
|
-
setUsageLoading(prev => ({ ...prev, [
|
|
373
|
+
usageInflightRef.current.add(key);
|
|
374
|
+
setUsageLoading(prev => ({ ...prev, [key]: true }));
|
|
296
375
|
try {
|
|
297
|
-
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider });
|
|
376
|
+
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account });
|
|
298
377
|
if (!mountedRef.current)
|
|
299
378
|
return;
|
|
300
|
-
setUsages(prev => ({ ...prev, [
|
|
379
|
+
setUsages(prev => ({ ...prev, [key]: usage }));
|
|
301
380
|
setUsageErrors((prev) => {
|
|
302
381
|
const next = { ...prev };
|
|
303
|
-
delete next[
|
|
382
|
+
delete next[key];
|
|
304
383
|
return next;
|
|
305
384
|
});
|
|
306
385
|
}
|
|
307
386
|
catch (error) {
|
|
308
387
|
if (mountedRef.current)
|
|
309
|
-
setUsageErrors(prev => ({ ...prev, [
|
|
388
|
+
setUsageErrors(prev => ({ ...prev, [key]: messageOf(error) }));
|
|
310
389
|
}
|
|
311
390
|
finally {
|
|
312
|
-
usageInflightRef.current.delete(
|
|
391
|
+
usageInflightRef.current.delete(key);
|
|
313
392
|
if (mountedRef.current)
|
|
314
|
-
setUsageLoading(prev => ({ ...prev, [
|
|
393
|
+
setUsageLoading(prev => ({ ...prev, [key]: false }));
|
|
315
394
|
}
|
|
316
395
|
}, [rpc]);
|
|
317
|
-
// Fetch usage once
|
|
318
|
-
//
|
|
319
|
-
// per-
|
|
396
|
+
// Fetch usage once an account is logged in; drop the snapshots of accounts
|
|
397
|
+
// that vanished so a re-login refetches. A failed lookup does not auto-retry
|
|
398
|
+
// — the per-account Refresh button is the retry path.
|
|
320
399
|
useEffect(() => {
|
|
400
|
+
const live = new Set();
|
|
321
401
|
for (const { id } of PROVIDERS) {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
void loadUsage(id);
|
|
328
|
-
}
|
|
329
|
-
else if (usages[id] !== undefined || usageErrors[id] !== undefined) {
|
|
330
|
-
setUsages((prev) => {
|
|
331
|
-
const next = { ...prev };
|
|
332
|
-
delete next[id];
|
|
333
|
-
return next;
|
|
334
|
-
});
|
|
335
|
-
setUsageErrors((prev) => {
|
|
336
|
-
const next = { ...prev };
|
|
337
|
-
delete next[id];
|
|
338
|
-
return next;
|
|
339
|
-
});
|
|
402
|
+
for (const account of statuses[id]?.accounts ?? []) {
|
|
403
|
+
const key = `${id}:${account.key}`;
|
|
404
|
+
live.add(key);
|
|
405
|
+
if (usages[key] === undefined && usageErrors[key] === undefined)
|
|
406
|
+
void loadUsage(id, account.key);
|
|
340
407
|
}
|
|
341
408
|
}
|
|
409
|
+
setUsages(prev => dropStale(prev, live));
|
|
410
|
+
setUsageErrors(prev => dropStale(prev, live));
|
|
342
411
|
}, [statuses, usages, usageErrors, loadUsage]);
|
|
343
|
-
const login = useCallback(async (provider) => {
|
|
412
|
+
const login = useCallback(async (provider, method) => {
|
|
344
413
|
if (rpc === undefined)
|
|
345
414
|
return;
|
|
346
415
|
setProviderError(provider, undefined);
|
|
347
416
|
try {
|
|
348
|
-
const response = await callSubscriptionsAuth(rpc, 'login', {
|
|
417
|
+
const response = await callSubscriptionsAuth(rpc, 'login', {
|
|
418
|
+
provider,
|
|
419
|
+
...method === undefined ? {} : { method },
|
|
420
|
+
});
|
|
349
421
|
if (typeof response.authorizeUrl === 'string' && response.authorizeUrl === '') {
|
|
350
422
|
// Instant login (e.g. imported from Claude Code credentials)
|
|
351
423
|
await refresh();
|
|
@@ -357,7 +429,10 @@ export function SubscriptionsSection(props) {
|
|
|
357
429
|
if (!mountedRef.current)
|
|
358
430
|
return;
|
|
359
431
|
// Optimistic busy so Cancel and the manual fallback appear before the first poll tick.
|
|
360
|
-
setStatuses(prev => ({
|
|
432
|
+
setStatuses(prev => ({
|
|
433
|
+
...prev,
|
|
434
|
+
[provider]: { accounts: prev[provider]?.accounts ?? [], ...prev[provider], busy: true },
|
|
435
|
+
}));
|
|
361
436
|
if (typeof response.userCode === 'string' && response.userCode.length > 0) {
|
|
362
437
|
// Device flow: show the code card instead of opening the page blind —
|
|
363
438
|
// the user copies the code first, then opens the verification page.
|
|
@@ -401,20 +476,32 @@ export function SubscriptionsSection(props) {
|
|
|
401
476
|
}
|
|
402
477
|
await refresh();
|
|
403
478
|
}, [rpc, manualDrafts, setProviderError, refresh]);
|
|
404
|
-
const logout = useCallback(async (provider, name) => {
|
|
479
|
+
const logout = useCallback(async (provider, account, display, name) => {
|
|
405
480
|
if (rpc === undefined)
|
|
406
481
|
return;
|
|
407
|
-
if (!window.confirm(t('
|
|
482
|
+
if (!window.confirm(t('logoutAccountConfirm', { provider: name, account: display })))
|
|
408
483
|
return;
|
|
409
484
|
setProviderError(provider, undefined);
|
|
410
485
|
try {
|
|
411
|
-
await callSubscriptionsAuth(rpc, 'logout', { provider });
|
|
486
|
+
await callSubscriptionsAuth(rpc, 'logout', { provider, account });
|
|
412
487
|
}
|
|
413
488
|
catch (error) {
|
|
414
489
|
setProviderError(provider, messageOf(error));
|
|
415
490
|
}
|
|
416
491
|
await refresh();
|
|
417
492
|
}, [rpc, t, setProviderError, refresh]);
|
|
493
|
+
const setDefault = useCallback(async (provider, account) => {
|
|
494
|
+
if (rpc === undefined)
|
|
495
|
+
return;
|
|
496
|
+
setProviderError(provider, undefined);
|
|
497
|
+
try {
|
|
498
|
+
await callSubscriptionsAuth(rpc, 'setDefault', { provider, account });
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
setProviderError(provider, messageOf(error));
|
|
502
|
+
}
|
|
503
|
+
await refresh();
|
|
504
|
+
}, [rpc, setProviderError, refresh]);
|
|
418
505
|
const copyDeviceCode = useCallback((provider, userCode) => {
|
|
419
506
|
void navigator.clipboard?.writeText(userCode).then(() => {
|
|
420
507
|
if (!mountedRef.current)
|
|
@@ -427,22 +514,131 @@ export function SubscriptionsSection(props) {
|
|
|
427
514
|
}, 1500);
|
|
428
515
|
}).catch(() => undefined);
|
|
429
516
|
}, []);
|
|
517
|
+
// Proxy configuration: load once on mount; the dialog drives proxySet/proxyTest.
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
if (rpc === undefined)
|
|
520
|
+
return;
|
|
521
|
+
let alive = true;
|
|
522
|
+
void callSubscriptionsAuth(rpc, 'proxyGet', {}).then((view) => {
|
|
523
|
+
if (!alive)
|
|
524
|
+
return;
|
|
525
|
+
setProxy(view);
|
|
526
|
+
setProxyLoadError(undefined);
|
|
527
|
+
}).catch((error) => {
|
|
528
|
+
if (alive)
|
|
529
|
+
setProxyLoadError(messageOf(error));
|
|
530
|
+
});
|
|
531
|
+
return () => { alive = false; };
|
|
532
|
+
}, [rpc]);
|
|
533
|
+
useEffect(() => {
|
|
534
|
+
if (!proxyOpen)
|
|
535
|
+
return;
|
|
536
|
+
const onKey = (event) => {
|
|
537
|
+
if (event.key === 'Escape')
|
|
538
|
+
setProxyOpen(false);
|
|
539
|
+
};
|
|
540
|
+
window.addEventListener('keydown', onKey);
|
|
541
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
542
|
+
}, [proxyOpen]);
|
|
543
|
+
const openProxyDialog = useCallback(() => {
|
|
544
|
+
if (proxy === undefined)
|
|
545
|
+
return;
|
|
546
|
+
setProxyEnabled(proxy.enabled);
|
|
547
|
+
setProxyUrl(proxy.url);
|
|
548
|
+
setProxyUsername(proxy.username ?? '');
|
|
549
|
+
setProxyPassword('');
|
|
550
|
+
setProxyClearPassword(false);
|
|
551
|
+
setProxyBypass(proxy.bypass.join(', '));
|
|
552
|
+
setProxyMessage(undefined);
|
|
553
|
+
setProxyTestResult(undefined);
|
|
554
|
+
setProxyOpen(true);
|
|
555
|
+
}, [proxy]);
|
|
556
|
+
const saveProxy = useCallback(async () => {
|
|
557
|
+
if (rpc === undefined)
|
|
558
|
+
return;
|
|
559
|
+
setProxySaving(true);
|
|
560
|
+
setProxyMessage(undefined);
|
|
561
|
+
try {
|
|
562
|
+
const view = await callSubscriptionsAuth(rpc, 'proxySet', {
|
|
563
|
+
enabled: proxyEnabled,
|
|
564
|
+
url: proxyUrl.trim(),
|
|
565
|
+
username: proxyUsername,
|
|
566
|
+
...proxyClearPassword ? { password: null } : proxyPassword !== '' ? { password: proxyPassword } : {},
|
|
567
|
+
bypass: proxyBypass.split(/[,\n]/).map(entry => entry.trim()).filter(entry => entry !== ''),
|
|
568
|
+
});
|
|
569
|
+
setProxy(view);
|
|
570
|
+
setProxyLoadError(undefined);
|
|
571
|
+
setProxyMessage({ tone: 'success', text: t('proxySaved') });
|
|
572
|
+
setProxyOpen(false);
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
setProxyMessage({ tone: 'error', text: t('proxySaveFailed', { message: messageOf(error) }) });
|
|
576
|
+
}
|
|
577
|
+
finally {
|
|
578
|
+
setProxySaving(false);
|
|
579
|
+
}
|
|
580
|
+
}, [rpc, proxyEnabled, proxyUrl, proxyUsername, proxyPassword, proxyClearPassword, proxyBypass, t]);
|
|
581
|
+
const testProxy = useCallback(async () => {
|
|
582
|
+
if (rpc === undefined || proxyTesting)
|
|
583
|
+
return;
|
|
584
|
+
setProxyTesting(true);
|
|
585
|
+
setProxyTestResult(undefined);
|
|
586
|
+
try {
|
|
587
|
+
// Test the dialog's current inputs (they do not need to be saved first);
|
|
588
|
+
// the host builds a throwaway agent for the probe.
|
|
589
|
+
setProxyTestResult(await callSubscriptionsAuth(rpc, 'proxyTest', {
|
|
590
|
+
proxy: {
|
|
591
|
+
url: proxyUrl.trim(),
|
|
592
|
+
...proxyUsername.trim() !== '' ? { username: proxyUsername.trim() } : {},
|
|
593
|
+
...proxyPassword !== '' ? { password: proxyPassword } : {},
|
|
594
|
+
},
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
setProxyTestResult({ ok: false, viaProxy: false, error: messageOf(error) });
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
setProxyTesting(false);
|
|
602
|
+
}
|
|
603
|
+
}, [rpc, proxyTesting, proxyUrl, proxyUsername, proxyPassword]);
|
|
430
604
|
if (rpc === undefined) {
|
|
431
605
|
return _jsx("p", { style: styles.intro, children: t('unavailable') });
|
|
432
606
|
}
|
|
433
|
-
return (_jsxs("div", { style: styles.section, children: [_jsx("p", { style: styles.intro, children: t('intro') }),
|
|
607
|
+
return (_jsxs("div", { style: styles.section, children: [_jsx("p", { style: styles.intro, children: t('intro') }), _jsxs("div", { style: styles.proxyCard, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: {
|
|
608
|
+
...styles.dot,
|
|
609
|
+
background: proxy?.enabled === true
|
|
610
|
+
? 'var(--dsw-alias-state-success-primary)'
|
|
611
|
+
: 'var(--dsw-alias-label-dimmed)',
|
|
612
|
+
} }), _jsx("span", { style: styles.name, children: t('proxyTitle') }), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: openProxyDialog, children: t('proxyConfigure') })] }), _jsx("p", { style: styles.statusLine, children: proxyStatusText(t, proxy, proxyLoadError) })] }), _jsx("div", { style: styles.separator }), PROVIDERS.map(({ id, name }) => {
|
|
434
613
|
const status = statuses[id];
|
|
435
614
|
const busy = status?.busy === true;
|
|
436
615
|
const deviceCode = deviceCodes[id];
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
616
|
+
const accounts = status?.accounts ?? [];
|
|
617
|
+
return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) } }), _jsx("span", { style: styles.name, children: name })] }), _jsx("p", { style: styles.statusLine, children: statusText(t, status) }), status?.detail !== undefined && status.detail !== '' && (_jsx("p", { style: styles.statusLine, children: status.detail })), errors[id] !== undefined && _jsx("p", { style: styles.errorLine, children: errors[id] }), accounts.map((account) => {
|
|
618
|
+
const usageKey = `${id}:${account.key}`;
|
|
619
|
+
const usage = usages[usageKey];
|
|
620
|
+
const usageError = usageErrors[usageKey];
|
|
621
|
+
const display = account.account ?? account.key;
|
|
622
|
+
// Providers without a usage endpoint answer supported:false — no block.
|
|
623
|
+
const showUsage = usage?.supported !== false
|
|
624
|
+
&& (usage !== undefined || usageError !== undefined || usageLoading[usageKey] === true);
|
|
625
|
+
return (_jsxs("div", { style: styles.accountRow, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('setDefault'), onClick: () => {
|
|
626
|
+
if (!account.isDefault)
|
|
627
|
+
void setDefault(id, account.key);
|
|
628
|
+
}, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', { date: new Date(account.expiresAt).toLocaleString() }) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(id, account.key, display, name); }, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: { ...styles.usageRefresh, ...usageLoading[usageKey] === true ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: usageLoading[usageKey] === true, onClick: () => { void loadUsage(id, account.key); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
|
|
629
|
+
const percent = Math.min(100, Math.max(0, window.usedPercent));
|
|
630
|
+
return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [`${String(Math.round(percent))}%`, window.resetsAt !== undefined
|
|
631
|
+
&& ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx("div", { style: styles.usageTrack, children: _jsx("div", { style: { ...styles.usageFill, width: `${String(percent)}%`, background: usageBarColor(percent) } }) })] }, index));
|
|
632
|
+
})] }))] }, account.key));
|
|
633
|
+
}), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'oauth'); }, children: t('addAccountOAuth') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'keychain'); }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('addAccount') })), busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(id); }, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { copyDeviceCode(id, deviceCode.userCode); }, children: copiedCode === id ? t('deviceCodeCopied') : t('deviceCodeCopy') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { window.open(deviceCode.verificationUrl, '_blank', 'noopener'); }, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value })) }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void submitManual(id); }, children: t('submit') })] })] }))] }, id));
|
|
634
|
+
}), proxyOpen && (_jsx("div", { style: styles.modalOverlay, onClick: () => setProxyOpen(false), children: _jsxs("div", { style: styles.modal, onClick: event => event.stopPropagation(), children: [_jsxs("div", { style: styles.modalHeader, children: [_jsx("span", { style: styles.modalTitle, children: t('proxyDialogTitle') }), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto' }, onClick: () => setProxyOpen(false), children: t('proxyDialogClose') })] }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyEnabled, onChange: event => setProxyEnabled(event.target.checked) }), _jsx("span", { children: t('proxyEnabled') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUrl') }), _jsx("input", { style: styles.proxyInput, value: proxyUrl, placeholder: t('proxyUrlPlaceholder'), onChange: event => setProxyUrl(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyUrlHint') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUsername') }), _jsx("input", { style: styles.proxyInput, value: proxyUsername, placeholder: t('proxyUsernamePlaceholder'), onChange: event => setProxyUsername(event.target.value) })] }), _jsxs("div", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyPassword') }), _jsx("input", { type: "password", style: styles.proxyInput, value: proxyPassword, placeholder: t('proxyPasswordPlaceholder'), onChange: event => setProxyPassword(event.target.value) }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyClearPassword, onChange: event => setProxyClearPassword(event.target.checked) }), _jsx("span", { children: t('proxyClearPassword') })] })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyBypass') }), _jsx("input", { style: styles.proxyInput, value: proxyBypass, placeholder: t('proxyBypassPlaceholder'), onChange: event => setProxyBypass(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyBypassHint') })] }), _jsx("p", { style: styles.proxyHint, children: t('proxyNote') }), proxyMessage !== undefined && (_jsx("p", { style: { ...styles.proxyMessage, color: messageColor(proxyMessage.tone) }, children: proxyMessage.text })), proxyTestResult !== undefined && (_jsx("p", { style: {
|
|
635
|
+
...styles.proxyMessage,
|
|
636
|
+
color: proxyTestResult.ok
|
|
637
|
+
? 'var(--dsw-alias-state-success-primary)'
|
|
638
|
+
: 'var(--dsw-alias-state-error-primary)',
|
|
639
|
+
}, children: proxyTestResult.ok
|
|
640
|
+
? (proxyTestResult.viaProxy
|
|
641
|
+
? t('proxyTestOk', { status: String(proxyTestResult.status), ms: String(proxyTestResult.latencyMs) })
|
|
642
|
+
: t('proxyTestOkDirect', { status: String(proxyTestResult.status), ms: String(proxyTestResult.latencyMs) }))
|
|
643
|
+
: t('proxyTestFail', { message: proxyTestResult.error ?? '' }) })), _jsxs("div", { style: styles.proxyActions, children: [_jsx("button", { type: "button", style: { ...styles.button, ...proxyTesting ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: proxyTesting, onClick: () => { void testProxy(); }, children: proxyTesting ? t('proxyTesting') : t('proxyTest') }), _jsx("button", { type: "button", style: { ...styles.button, ...proxySaving ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: proxySaving, onClick: () => { void saveProxy(); }, children: proxySaving ? t('proxySaving') : t('proxySave') }), _jsx("button", { type: "button", style: styles.button, onClick: () => setProxyOpen(false), children: t('proxyCancel') })] })] }) }))] }));
|
|
448
644
|
}
|
package/lib/client/index.js
CHANGED
|
@@ -22,6 +22,17 @@ export const inject = ['slots', 'connection', 'locale'];
|
|
|
22
22
|
*/
|
|
23
23
|
export function apply(ctx) {
|
|
24
24
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-subscriptions: copy dictionaries');
|
|
25
|
+
// Settings-shell nudge: the panel (nav title + header row + section body)
|
|
26
|
+
// sits flush against the panel's top edge; push it down a little to leave
|
|
27
|
+
// breathing room. Scoped by the settings panel's own dialog role + nav child
|
|
28
|
+
// so other aria-modal dialogs (e.g. the attachment lightbox) are untouched.
|
|
29
|
+
ctx.effect(() => {
|
|
30
|
+
const style = document.createElement('style');
|
|
31
|
+
style.setAttribute('data-plugin', 'dsh-plugin-subscriptions');
|
|
32
|
+
style.textContent = 'div[role="dialog"][aria-modal="true"]:has(> nav) { padding-top: 14px; }';
|
|
33
|
+
document.head.appendChild(style);
|
|
34
|
+
return () => style.remove();
|
|
35
|
+
}, 'dsh-plugin-subscriptions: settings panel breathing room');
|
|
25
36
|
// The client-runtime Context merge types `connection` as the host handle;
|
|
26
37
|
// in the browser shell the same key holds the full client ConnectionHandle.
|
|
27
38
|
const connection = ctx.get('connection');
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -6,15 +6,19 @@ export declare const en: {
|
|
|
6
6
|
unavailable: string;
|
|
7
7
|
checking: string;
|
|
8
8
|
loginInProgress: string;
|
|
9
|
-
loggedIn: string;
|
|
10
|
-
loggedInAccount: string;
|
|
11
|
-
loggedInExpires: string;
|
|
12
|
-
loggedInAccountExpires: string;
|
|
13
9
|
notLoggedIn: string;
|
|
10
|
+
loggedInCount: string;
|
|
11
|
+
accountExpires: string;
|
|
12
|
+
defaultBadge: string;
|
|
13
|
+
setDefault: string;
|
|
14
|
+
addAccount: string;
|
|
15
|
+
addAccountOAuth: string;
|
|
16
|
+
addAccountKeychain: string;
|
|
17
|
+
addAccountHint: string;
|
|
14
18
|
login: string;
|
|
15
19
|
cancel: string;
|
|
16
20
|
logout: string;
|
|
17
|
-
|
|
21
|
+
logoutAccountConfirm: string;
|
|
18
22
|
manualSummary: string;
|
|
19
23
|
manualPlaceholder: string;
|
|
20
24
|
submit: string;
|
|
@@ -51,6 +55,38 @@ export declare const en: {
|
|
|
51
55
|
speedFastDescription: string;
|
|
52
56
|
commandFast: string;
|
|
53
57
|
commandFastUnavailable: string;
|
|
58
|
+
proxyTitle: string;
|
|
59
|
+
proxyStatusNone: string;
|
|
60
|
+
proxyStatusEnabled: string;
|
|
61
|
+
proxyStatusError: string;
|
|
62
|
+
proxyConfigure: string;
|
|
63
|
+
proxyDialogTitle: string;
|
|
64
|
+
proxyDialogClose: string;
|
|
65
|
+
proxyEnabled: string;
|
|
66
|
+
proxyUrl: string;
|
|
67
|
+
proxyUrlPlaceholder: string;
|
|
68
|
+
proxyUrlHint: string;
|
|
69
|
+
proxyUsername: string;
|
|
70
|
+
proxyUsernamePlaceholder: string;
|
|
71
|
+
proxyPassword: string;
|
|
72
|
+
proxyPasswordPlaceholder: string;
|
|
73
|
+
proxyClearPassword: string;
|
|
74
|
+
proxyBypass: string;
|
|
75
|
+
proxyBypassPlaceholder: string;
|
|
76
|
+
proxyBypassHint: string;
|
|
77
|
+
proxyTest: string;
|
|
78
|
+
proxyTesting: string;
|
|
79
|
+
proxyTestOk: string;
|
|
80
|
+
proxyTestOkDirect: string;
|
|
81
|
+
proxyTestFail: string;
|
|
82
|
+
proxySave: string;
|
|
83
|
+
proxyCancel: string;
|
|
84
|
+
proxySaving: string;
|
|
85
|
+
proxyLoading: string;
|
|
86
|
+
proxyLoadFailed: string;
|
|
87
|
+
proxySaved: string;
|
|
88
|
+
proxySaveFailed: string;
|
|
89
|
+
proxyNote: string;
|
|
54
90
|
};
|
|
55
91
|
/** zh strings, one per {@link en} key. */
|
|
56
92
|
export declare const zh: {
|
|
@@ -59,15 +95,19 @@ export declare const zh: {
|
|
|
59
95
|
unavailable: string;
|
|
60
96
|
checking: string;
|
|
61
97
|
loginInProgress: string;
|
|
62
|
-
loggedIn: string;
|
|
63
|
-
loggedInAccount: string;
|
|
64
|
-
loggedInExpires: string;
|
|
65
|
-
loggedInAccountExpires: string;
|
|
66
98
|
notLoggedIn: string;
|
|
99
|
+
loggedInCount: string;
|
|
100
|
+
accountExpires: string;
|
|
101
|
+
defaultBadge: string;
|
|
102
|
+
setDefault: string;
|
|
103
|
+
addAccount: string;
|
|
104
|
+
addAccountOAuth: string;
|
|
105
|
+
addAccountKeychain: string;
|
|
106
|
+
addAccountHint: string;
|
|
67
107
|
login: string;
|
|
68
108
|
cancel: string;
|
|
69
109
|
logout: string;
|
|
70
|
-
|
|
110
|
+
logoutAccountConfirm: string;
|
|
71
111
|
manualSummary: string;
|
|
72
112
|
manualPlaceholder: string;
|
|
73
113
|
submit: string;
|
|
@@ -104,6 +144,38 @@ export declare const zh: {
|
|
|
104
144
|
speedFastDescription: string;
|
|
105
145
|
commandFast: string;
|
|
106
146
|
commandFastUnavailable: string;
|
|
147
|
+
proxyTitle: string;
|
|
148
|
+
proxyStatusNone: string;
|
|
149
|
+
proxyStatusEnabled: string;
|
|
150
|
+
proxyStatusError: string;
|
|
151
|
+
proxyConfigure: string;
|
|
152
|
+
proxyDialogTitle: string;
|
|
153
|
+
proxyDialogClose: string;
|
|
154
|
+
proxyEnabled: string;
|
|
155
|
+
proxyUrl: string;
|
|
156
|
+
proxyUrlPlaceholder: string;
|
|
157
|
+
proxyUrlHint: string;
|
|
158
|
+
proxyUsername: string;
|
|
159
|
+
proxyUsernamePlaceholder: string;
|
|
160
|
+
proxyPassword: string;
|
|
161
|
+
proxyPasswordPlaceholder: string;
|
|
162
|
+
proxyClearPassword: string;
|
|
163
|
+
proxyBypass: string;
|
|
164
|
+
proxyBypassPlaceholder: string;
|
|
165
|
+
proxyBypassHint: string;
|
|
166
|
+
proxyTest: string;
|
|
167
|
+
proxyTesting: string;
|
|
168
|
+
proxyTestOk: string;
|
|
169
|
+
proxyTestOkDirect: string;
|
|
170
|
+
proxyTestFail: string;
|
|
171
|
+
proxySave: string;
|
|
172
|
+
proxyCancel: string;
|
|
173
|
+
proxySaving: string;
|
|
174
|
+
proxyLoading: string;
|
|
175
|
+
proxyLoadFailed: string;
|
|
176
|
+
proxySaved: string;
|
|
177
|
+
proxySaveFailed: string;
|
|
178
|
+
proxyNote: string;
|
|
107
179
|
};
|
|
108
180
|
/** The Subscriptions namespace key union (en is the key-set source of truth). */
|
|
109
181
|
export type SubscriptionsKey = keyof typeof en;
|