dsh-plugin-subscriptions 0.1.0
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 +93 -0
- package/README.zh.md +93 -0
- package/cordis.patch.yml +12 -0
- package/lib/auth/jwt.d.ts +10 -0
- package/lib/auth/jwt.js +25 -0
- package/lib/auth/oauth-flow.d.ts +91 -0
- package/lib/auth/oauth-flow.js +227 -0
- package/lib/auth/pkce.d.ts +31 -0
- package/lib/auth/pkce.js +35 -0
- package/lib/auth/rpc.d.ts +51 -0
- package/lib/auth/rpc.js +83 -0
- package/lib/auth/store.d.ts +90 -0
- package/lib/auth/store.js +137 -0
- package/lib/client/SubscriptionsSection.d.ts +30 -0
- package/lib/client/SubscriptionsSection.js +290 -0
- package/lib/client/index.d.ts +31 -0
- package/lib/client/index.js +35 -0
- package/lib/client/locales.d.ts +45 -0
- package/lib/client/locales.js +43 -0
- package/lib/client.js +546 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +34 -0
- package/lib/index.js +2932 -0
- package/lib/providers/claude.d.ts +60 -0
- package/lib/providers/claude.js +243 -0
- package/lib/providers/codex.d.ts +96 -0
- package/lib/providers/codex.js +391 -0
- package/lib/providers/common.d.ts +185 -0
- package/lib/providers/common.js +302 -0
- package/lib/providers/grok.d.ts +90 -0
- package/lib/providers/grok.js +337 -0
- package/lib/tools/image-generate.d.ts +60 -0
- package/lib/tools/image-generate.js +142 -0
- package/lib/tools/x-search.d.ts +58 -0
- package/lib/tools/x-search.js +195 -0
- package/lib/translate/anthropic.d.ts +120 -0
- package/lib/translate/anthropic.js +370 -0
- package/lib/translate/resolved.d.ts +35 -0
- package/lib/translate/resolved.js +40 -0
- package/lib/translate/responses.d.ts +127 -0
- package/lib/translate/responses.js +352 -0
- package/lib/translate/sse.d.ts +21 -0
- package/lib/translate/sse.js +56 -0
- package/package.json +83 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// `.js` extension: this package's tsconfig lacks the reference repo's
|
|
2
|
+
// allowImportingTsExtensions/rewriteRelativeImportExtensions pair; under
|
|
3
|
+
// nodenext the .js specifier resolves to the .tsx source (see README note).
|
|
4
|
+
import { SubscriptionsSection } from './SubscriptionsSection.js';
|
|
5
|
+
import { en, zh } from './locales.js';
|
|
6
|
+
/** Dictionary namespace owned by this plugin. */
|
|
7
|
+
const NS = 'settings.subscriptions';
|
|
8
|
+
/**
|
|
9
|
+
* Required services (cordis fiber inject): `slots` carries the registration
|
|
10
|
+
* seat, `connection` the `/subscriptions-auth` RPC caller, and `locale` the copy
|
|
11
|
+
* dictionaries.
|
|
12
|
+
*/
|
|
13
|
+
export const inject = ['slots', 'connection', 'locale'];
|
|
14
|
+
/**
|
|
15
|
+
* Register the Subscriptions section once the `settings.section` declaration
|
|
16
|
+
* is on the ledger (the shell's apply order relative to this one is NOT
|
|
17
|
+
* constrained; registration depends on the slot through `slots.inject()`).
|
|
18
|
+
* @param ctx - client root context.
|
|
19
|
+
*/
|
|
20
|
+
export function apply(ctx) {
|
|
21
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-subscriptions: copy dictionaries');
|
|
22
|
+
// The client-runtime Context merge types `connection` as the host handle;
|
|
23
|
+
// in the browser shell the same key holds the full client ConnectionHandle.
|
|
24
|
+
const connection = ctx.get('connection');
|
|
25
|
+
const t = ctx.locale.bind(NS);
|
|
26
|
+
const injected = () => ({ rpc: connection.rpc, t });
|
|
27
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
28
|
+
name: 'settings.section',
|
|
29
|
+
id: 'subscriptions',
|
|
30
|
+
order: 90,
|
|
31
|
+
// A thunk re-evaluated per read, so the nav label follows the active locale.
|
|
32
|
+
label: () => t('nav'),
|
|
33
|
+
inject: injected,
|
|
34
|
+
}, SubscriptionsSection));
|
|
35
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Copy dictionaries for the Subscriptions settings section. */
|
|
2
|
+
/** English strings (the key-set source of truth for this pair). */
|
|
3
|
+
export declare const en: {
|
|
4
|
+
nav: string;
|
|
5
|
+
intro: string;
|
|
6
|
+
unavailable: string;
|
|
7
|
+
checking: string;
|
|
8
|
+
loginInProgress: string;
|
|
9
|
+
loggedIn: string;
|
|
10
|
+
loggedInAccount: string;
|
|
11
|
+
loggedInExpires: string;
|
|
12
|
+
loggedInAccountExpires: string;
|
|
13
|
+
notLoggedIn: string;
|
|
14
|
+
login: string;
|
|
15
|
+
cancel: string;
|
|
16
|
+
logout: string;
|
|
17
|
+
logoutConfirm: string;
|
|
18
|
+
manualSummary: string;
|
|
19
|
+
manualPlaceholder: string;
|
|
20
|
+
submit: string;
|
|
21
|
+
loginMissingUrl: string;
|
|
22
|
+
};
|
|
23
|
+
/** zh strings, one per {@link en} key. */
|
|
24
|
+
export declare const zh: {
|
|
25
|
+
nav: string;
|
|
26
|
+
intro: string;
|
|
27
|
+
unavailable: string;
|
|
28
|
+
checking: string;
|
|
29
|
+
loginInProgress: string;
|
|
30
|
+
loggedIn: string;
|
|
31
|
+
loggedInAccount: string;
|
|
32
|
+
loggedInExpires: string;
|
|
33
|
+
loggedInAccountExpires: string;
|
|
34
|
+
notLoggedIn: string;
|
|
35
|
+
login: string;
|
|
36
|
+
cancel: string;
|
|
37
|
+
logout: string;
|
|
38
|
+
logoutConfirm: string;
|
|
39
|
+
manualSummary: string;
|
|
40
|
+
manualPlaceholder: string;
|
|
41
|
+
submit: string;
|
|
42
|
+
loginMissingUrl: string;
|
|
43
|
+
};
|
|
44
|
+
/** The Subscriptions namespace key union (en is the key-set source of truth). */
|
|
45
|
+
export type SubscriptionsKey = keyof typeof en;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Copy dictionaries for the Subscriptions settings section. */
|
|
2
|
+
/** English strings (the key-set source of truth for this pair). */
|
|
3
|
+
export const en = {
|
|
4
|
+
nav: 'Subscriptions',
|
|
5
|
+
intro: 'Log a subscription provider in or out. Login opens the provider’s authorization page in a new tab; headless setups can paste the callback URL or code instead.',
|
|
6
|
+
unavailable: 'Connection unavailable; subscription status cannot be loaded.',
|
|
7
|
+
checking: 'Checking…',
|
|
8
|
+
loginInProgress: 'Login in progress…',
|
|
9
|
+
loggedIn: 'Logged in',
|
|
10
|
+
loggedInAccount: 'Logged in as {account}',
|
|
11
|
+
loggedInExpires: 'Logged in · expires {date}',
|
|
12
|
+
loggedInAccountExpires: 'Logged in as {account} · expires {date}',
|
|
13
|
+
notLoggedIn: 'Not logged in',
|
|
14
|
+
login: 'Log in',
|
|
15
|
+
cancel: 'Cancel',
|
|
16
|
+
logout: 'Log out',
|
|
17
|
+
logoutConfirm: 'Log out of {provider}?',
|
|
18
|
+
manualSummary: 'Browser flow not working? Paste the callback URL or code',
|
|
19
|
+
manualPlaceholder: 'Paste the callback URL or code',
|
|
20
|
+
submit: 'Submit',
|
|
21
|
+
loginMissingUrl: 'login answered without an authorizeUrl',
|
|
22
|
+
};
|
|
23
|
+
/** zh strings, one per {@link en} key. */
|
|
24
|
+
export const zh = {
|
|
25
|
+
nav: '订阅',
|
|
26
|
+
intro: '在此登录或退出订阅服务商。点击登录会在新标签页打开服务商的授权页面;无浏览器环境可改为粘贴回调 URL 或授权码。',
|
|
27
|
+
unavailable: '连接不可用,无法加载订阅状态。',
|
|
28
|
+
checking: '查询中…',
|
|
29
|
+
loginInProgress: '登录中…',
|
|
30
|
+
loggedIn: '已登录',
|
|
31
|
+
loggedInAccount: '已登录:{account}',
|
|
32
|
+
loggedInExpires: '已登录 · 过期时间 {date}',
|
|
33
|
+
loggedInAccountExpires: '已登录:{account} · 过期时间 {date}',
|
|
34
|
+
notLoggedIn: '未登录',
|
|
35
|
+
login: '登录',
|
|
36
|
+
cancel: '取消',
|
|
37
|
+
logout: '退出登录',
|
|
38
|
+
logoutConfirm: '确定退出 {provider} 的登录吗?',
|
|
39
|
+
manualSummary: '浏览器流程无法完成?粘贴回调 URL 或授权码',
|
|
40
|
+
manualPlaceholder: '粘贴回调 URL 或授权码',
|
|
41
|
+
submit: '提交',
|
|
42
|
+
loginMissingUrl: 'login 响应缺少 authorizeUrl',
|
|
43
|
+
};
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-plugin-subscriptions", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react);
|
|
28
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
+
react_jsx_runtime = __toESM(react_jsx_runtime);
|
|
30
|
+
|
|
31
|
+
//#region src/client/locales.ts
|
|
32
|
+
/** Copy dictionaries for the Subscriptions settings section. */
|
|
33
|
+
/** English strings (the key-set source of truth for this pair). */
|
|
34
|
+
const en = {
|
|
35
|
+
nav: "Subscriptions",
|
|
36
|
+
intro: "Log a subscription provider in or out. Login opens the provider’s authorization page in a new tab; headless setups can paste the callback URL or code instead.",
|
|
37
|
+
unavailable: "Connection unavailable; subscription status cannot be loaded.",
|
|
38
|
+
checking: "Checking…",
|
|
39
|
+
loginInProgress: "Login in progress…",
|
|
40
|
+
loggedIn: "Logged in",
|
|
41
|
+
loggedInAccount: "Logged in as {account}",
|
|
42
|
+
loggedInExpires: "Logged in · expires {date}",
|
|
43
|
+
loggedInAccountExpires: "Logged in as {account} · expires {date}",
|
|
44
|
+
notLoggedIn: "Not logged in",
|
|
45
|
+
login: "Log in",
|
|
46
|
+
cancel: "Cancel",
|
|
47
|
+
logout: "Log out",
|
|
48
|
+
logoutConfirm: "Log out of {provider}?",
|
|
49
|
+
manualSummary: "Browser flow not working? Paste the callback URL or code",
|
|
50
|
+
manualPlaceholder: "Paste the callback URL or code",
|
|
51
|
+
submit: "Submit",
|
|
52
|
+
loginMissingUrl: "login answered without an authorizeUrl"
|
|
53
|
+
};
|
|
54
|
+
/** zh strings, one per {@link en} key. */
|
|
55
|
+
const zh = {
|
|
56
|
+
nav: "订阅",
|
|
57
|
+
intro: "在此登录或退出订阅服务商。点击登录会在新标签页打开服务商的授权页面;无浏览器环境可改为粘贴回调 URL 或授权码。",
|
|
58
|
+
unavailable: "连接不可用,无法加载订阅状态。",
|
|
59
|
+
checking: "查询中…",
|
|
60
|
+
loginInProgress: "登录中…",
|
|
61
|
+
loggedIn: "已登录",
|
|
62
|
+
loggedInAccount: "已登录:{account}",
|
|
63
|
+
loggedInExpires: "已登录 · 过期时间 {date}",
|
|
64
|
+
loggedInAccountExpires: "已登录:{account} · 过期时间 {date}",
|
|
65
|
+
notLoggedIn: "未登录",
|
|
66
|
+
login: "登录",
|
|
67
|
+
cancel: "取消",
|
|
68
|
+
logout: "退出登录",
|
|
69
|
+
logoutConfirm: "确定退出 {provider} 的登录吗?",
|
|
70
|
+
manualSummary: "浏览器流程无法完成?粘贴回调 URL 或授权码",
|
|
71
|
+
manualPlaceholder: "粘贴回调 URL 或授权码",
|
|
72
|
+
submit: "提交",
|
|
73
|
+
loginMissingUrl: "login 响应缺少 authorizeUrl"
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/client/SubscriptionsSection.tsx
|
|
78
|
+
/** Logical RPC channel served by the node half of this plugin. */
|
|
79
|
+
const SUBSCRIPTIONS_AUTH_CHANNEL = "/subscriptions-auth";
|
|
80
|
+
/** Poll cadence while a provider login attempt is busy. */
|
|
81
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
82
|
+
/** Card display metadata, in page order (names are brand names, not translated). */
|
|
83
|
+
const PROVIDERS = [
|
|
84
|
+
{
|
|
85
|
+
id: "codex",
|
|
86
|
+
name: "Codex (ChatGPT)"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "claude",
|
|
90
|
+
name: "Claude"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "grok",
|
|
94
|
+
name: "Grok (X Premium)"
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
/** Business error returned by the `/subscriptions-auth` channel (error branch message). */
|
|
98
|
+
var SubscriptionsAuthError = class extends Error {};
|
|
99
|
+
/**
|
|
100
|
+
* Call one `/subscriptions-auth` endpoint and unwrap the business result.
|
|
101
|
+
* @param rpc - Connection RPC caller.
|
|
102
|
+
* @param endpoint - channel-relative endpoint.
|
|
103
|
+
* @param payload - channel-owned request payload.
|
|
104
|
+
* @returns the success value, cast by the caller to the endpoint's shape.
|
|
105
|
+
*/
|
|
106
|
+
async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
107
|
+
let result;
|
|
108
|
+
try {
|
|
109
|
+
result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL, endpoint, payload);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new SubscriptionsAuthError(error instanceof Error ? error.message : String(error));
|
|
112
|
+
}
|
|
113
|
+
if (!result.ok) throw new SubscriptionsAuthError(result.error.message);
|
|
114
|
+
return result.value;
|
|
115
|
+
}
|
|
116
|
+
/** Human text of an action failure, SubscriptionsAuthError or not. */
|
|
117
|
+
function messageOf(error) {
|
|
118
|
+
return error instanceof Error ? error.message : String(error);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* English-dictionary fallback for a missing inject `t` (standalone renders);
|
|
122
|
+
* the slot inject always supplies the locale-bound one.
|
|
123
|
+
* @param key - dictionary key.
|
|
124
|
+
* @param params - `{name}` template params.
|
|
125
|
+
* @returns the template with params substituted.
|
|
126
|
+
*/
|
|
127
|
+
function fallbackTranslate(key, params) {
|
|
128
|
+
let text = en[key];
|
|
129
|
+
for (const [name, value] of Object.entries(params ?? {})) text = text.replaceAll(`{${name}}`, String(value));
|
|
130
|
+
return text;
|
|
131
|
+
}
|
|
132
|
+
const styles = {
|
|
133
|
+
section: {
|
|
134
|
+
display: "flex",
|
|
135
|
+
flexDirection: "column",
|
|
136
|
+
gap: 12,
|
|
137
|
+
maxWidth: 560,
|
|
138
|
+
color: "var(--dsw-alias-label-primary)"
|
|
139
|
+
},
|
|
140
|
+
intro: {
|
|
141
|
+
margin: 0,
|
|
142
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
143
|
+
fontSize: 14,
|
|
144
|
+
lineHeight: "22px"
|
|
145
|
+
},
|
|
146
|
+
card: {
|
|
147
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
148
|
+
borderRadius: 12,
|
|
149
|
+
padding: "12px 14px",
|
|
150
|
+
display: "flex",
|
|
151
|
+
flexDirection: "column",
|
|
152
|
+
gap: 6
|
|
153
|
+
},
|
|
154
|
+
cardHeader: {
|
|
155
|
+
display: "flex",
|
|
156
|
+
alignItems: "center",
|
|
157
|
+
gap: 8
|
|
158
|
+
},
|
|
159
|
+
dot: {
|
|
160
|
+
width: 8,
|
|
161
|
+
height: 8,
|
|
162
|
+
borderRadius: "50%",
|
|
163
|
+
flexShrink: 0
|
|
164
|
+
},
|
|
165
|
+
name: {
|
|
166
|
+
fontWeight: 500,
|
|
167
|
+
fontSize: 14,
|
|
168
|
+
lineHeight: "22px",
|
|
169
|
+
color: "var(--dsw-alias-label-primary)"
|
|
170
|
+
},
|
|
171
|
+
statusLine: {
|
|
172
|
+
margin: 0,
|
|
173
|
+
fontSize: 12,
|
|
174
|
+
lineHeight: "18px",
|
|
175
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
176
|
+
},
|
|
177
|
+
errorLine: {
|
|
178
|
+
margin: 0,
|
|
179
|
+
fontSize: 12,
|
|
180
|
+
lineHeight: "18px",
|
|
181
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
182
|
+
},
|
|
183
|
+
actions: {
|
|
184
|
+
display: "flex",
|
|
185
|
+
gap: 8,
|
|
186
|
+
marginTop: 4,
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
flexWrap: "wrap"
|
|
189
|
+
},
|
|
190
|
+
button: {
|
|
191
|
+
boxSizing: "border-box",
|
|
192
|
+
display: "inline-flex",
|
|
193
|
+
alignItems: "center",
|
|
194
|
+
justifyContent: "center",
|
|
195
|
+
height: 28,
|
|
196
|
+
padding: "0 10px",
|
|
197
|
+
borderRadius: 14,
|
|
198
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
199
|
+
background: "transparent",
|
|
200
|
+
color: "var(--dsw-alias-label-primary)",
|
|
201
|
+
font: "inherit",
|
|
202
|
+
fontSize: 12,
|
|
203
|
+
lineHeight: "18px",
|
|
204
|
+
cursor: "pointer"
|
|
205
|
+
},
|
|
206
|
+
manual: {
|
|
207
|
+
marginTop: 4,
|
|
208
|
+
fontSize: 12,
|
|
209
|
+
lineHeight: "18px",
|
|
210
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
211
|
+
},
|
|
212
|
+
manualRow: {
|
|
213
|
+
display: "flex",
|
|
214
|
+
gap: 8,
|
|
215
|
+
marginTop: 6
|
|
216
|
+
},
|
|
217
|
+
manualInput: {
|
|
218
|
+
flex: 1,
|
|
219
|
+
height: 32,
|
|
220
|
+
boxSizing: "border-box",
|
|
221
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
222
|
+
borderRadius: 8,
|
|
223
|
+
padding: "0 10px",
|
|
224
|
+
font: "inherit",
|
|
225
|
+
fontSize: 14,
|
|
226
|
+
lineHeight: "22px",
|
|
227
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
228
|
+
color: "var(--dsw-alias-label-primary)"
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
/** Status dot color for one provider state. */
|
|
232
|
+
function dotColor(status) {
|
|
233
|
+
if (status?.busy === true) return "var(--dsw-alias-state-warn-label)";
|
|
234
|
+
if (status?.loggedIn === true) return "var(--dsw-alias-state-success-primary)";
|
|
235
|
+
return "var(--dsw-alias-label-dimmed)";
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* One-line status text for one provider state.
|
|
239
|
+
* @param t - section translate.
|
|
240
|
+
* @param status - the provider's last reported state.
|
|
241
|
+
* @returns the localized status line.
|
|
242
|
+
*/
|
|
243
|
+
function statusText(t, status) {
|
|
244
|
+
if (status === void 0) return t("checking");
|
|
245
|
+
if (status.busy) return t("loginInProgress");
|
|
246
|
+
if (status.loggedIn) {
|
|
247
|
+
const params = {};
|
|
248
|
+
if (status.account !== void 0) params.account = status.account;
|
|
249
|
+
if (status.expiresAt !== void 0) params.date = new Date(status.expiresAt).toLocaleString();
|
|
250
|
+
if (params.account !== void 0 && params.date !== void 0) return t("loggedInAccountExpires", params);
|
|
251
|
+
if (params.account !== void 0) return t("loggedInAccount", params);
|
|
252
|
+
if (params.date !== void 0) return t("loggedInExpires", params);
|
|
253
|
+
return t("loggedIn");
|
|
254
|
+
}
|
|
255
|
+
return t("notLoggedIn");
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The Subscriptions settings page component.
|
|
259
|
+
* @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
|
|
260
|
+
* @returns the section body, or a notice while the RPC face is absent.
|
|
261
|
+
*/
|
|
262
|
+
function SubscriptionsSection(props) {
|
|
263
|
+
const { rpc } = props;
|
|
264
|
+
const t = props.t ?? fallbackTranslate;
|
|
265
|
+
const [statuses, setStatuses] = (0, react.useState)({});
|
|
266
|
+
const [errors, setErrors] = (0, react.useState)({});
|
|
267
|
+
const [manualDrafts, setManualDrafts] = (0, react.useState)({
|
|
268
|
+
codex: "",
|
|
269
|
+
claude: "",
|
|
270
|
+
grok: ""
|
|
271
|
+
});
|
|
272
|
+
const mountedRef = (0, react.useRef)(true);
|
|
273
|
+
const pollersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
274
|
+
const setProviderError = (0, react.useCallback)((provider, message) => {
|
|
275
|
+
if (!mountedRef.current) return;
|
|
276
|
+
setErrors((prev) => {
|
|
277
|
+
const next = { ...prev };
|
|
278
|
+
if (message === void 0) delete next[provider];
|
|
279
|
+
else next[provider] = message;
|
|
280
|
+
return next;
|
|
281
|
+
});
|
|
282
|
+
}, []);
|
|
283
|
+
const stopPolling = (0, react.useCallback)((provider) => {
|
|
284
|
+
const poller = pollersRef.current.get(provider);
|
|
285
|
+
if (poller !== void 0) {
|
|
286
|
+
clearInterval(poller);
|
|
287
|
+
pollersRef.current.delete(provider);
|
|
288
|
+
}
|
|
289
|
+
}, []);
|
|
290
|
+
/** Refetch every provider's status; stop a provider's poller once its attempt settles. */
|
|
291
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
292
|
+
if (rpc === void 0) return;
|
|
293
|
+
let response;
|
|
294
|
+
try {
|
|
295
|
+
response = await callSubscriptionsAuth(rpc, "status", {});
|
|
296
|
+
} catch {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!mountedRef.current) return;
|
|
300
|
+
setStatuses(response.providers);
|
|
301
|
+
for (const { id } of PROVIDERS) {
|
|
302
|
+
const status = response.providers[id];
|
|
303
|
+
if (status.loggedIn || !status.busy) stopPolling(id);
|
|
304
|
+
}
|
|
305
|
+
}, [rpc, stopPolling]);
|
|
306
|
+
const startPolling = (0, react.useCallback)((provider) => {
|
|
307
|
+
if (pollersRef.current.has(provider)) return;
|
|
308
|
+
pollersRef.current.set(provider, setInterval(() => {
|
|
309
|
+
refresh();
|
|
310
|
+
}, POLL_INTERVAL_MS));
|
|
311
|
+
}, [refresh]);
|
|
312
|
+
(0, react.useEffect)(() => {
|
|
313
|
+
mountedRef.current = true;
|
|
314
|
+
refresh().then(() => {
|
|
315
|
+
if (!mountedRef.current) return;
|
|
316
|
+
setStatuses((current) => {
|
|
317
|
+
for (const { id } of PROVIDERS) if (current[id]?.busy === true) startPolling(id);
|
|
318
|
+
return current;
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
return () => {
|
|
322
|
+
mountedRef.current = false;
|
|
323
|
+
for (const poller of pollersRef.current.values()) clearInterval(poller);
|
|
324
|
+
pollersRef.current.clear();
|
|
325
|
+
};
|
|
326
|
+
}, [refresh, startPolling]);
|
|
327
|
+
const login = (0, react.useCallback)(async (provider) => {
|
|
328
|
+
if (rpc === void 0) return;
|
|
329
|
+
setProviderError(provider, void 0);
|
|
330
|
+
try {
|
|
331
|
+
const response = await callSubscriptionsAuth(rpc, "login", { provider });
|
|
332
|
+
if (typeof response.authorizeUrl !== "string" || response.authorizeUrl === "") throw new SubscriptionsAuthError(t("loginMissingUrl"));
|
|
333
|
+
window.open(response.authorizeUrl, "_blank", "noopener");
|
|
334
|
+
if (!mountedRef.current) return;
|
|
335
|
+
setStatuses((prev) => ({
|
|
336
|
+
...prev,
|
|
337
|
+
[provider]: {
|
|
338
|
+
...prev[provider],
|
|
339
|
+
busy: true,
|
|
340
|
+
loggedIn: false
|
|
341
|
+
}
|
|
342
|
+
}));
|
|
343
|
+
startPolling(provider);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
setProviderError(provider, messageOf(error));
|
|
346
|
+
}
|
|
347
|
+
}, [
|
|
348
|
+
rpc,
|
|
349
|
+
t,
|
|
350
|
+
setProviderError,
|
|
351
|
+
startPolling
|
|
352
|
+
]);
|
|
353
|
+
const cancel = (0, react.useCallback)(async (provider) => {
|
|
354
|
+
if (rpc === void 0) return;
|
|
355
|
+
stopPolling(provider);
|
|
356
|
+
try {
|
|
357
|
+
await callSubscriptionsAuth(rpc, "cancel", { provider });
|
|
358
|
+
} catch (error) {
|
|
359
|
+
setProviderError(provider, messageOf(error));
|
|
360
|
+
}
|
|
361
|
+
await refresh();
|
|
362
|
+
}, [
|
|
363
|
+
rpc,
|
|
364
|
+
stopPolling,
|
|
365
|
+
setProviderError,
|
|
366
|
+
refresh
|
|
367
|
+
]);
|
|
368
|
+
const submitManual = (0, react.useCallback)(async (provider) => {
|
|
369
|
+
if (rpc === void 0) return;
|
|
370
|
+
const input = manualDrafts[provider].trim();
|
|
371
|
+
if (input === "") return;
|
|
372
|
+
setProviderError(provider, void 0);
|
|
373
|
+
try {
|
|
374
|
+
await callSubscriptionsAuth(rpc, "manual", {
|
|
375
|
+
provider,
|
|
376
|
+
input
|
|
377
|
+
});
|
|
378
|
+
if (mountedRef.current) setManualDrafts((prev) => ({
|
|
379
|
+
...prev,
|
|
380
|
+
[provider]: ""
|
|
381
|
+
}));
|
|
382
|
+
} catch (error) {
|
|
383
|
+
setProviderError(provider, messageOf(error));
|
|
384
|
+
}
|
|
385
|
+
await refresh();
|
|
386
|
+
}, [
|
|
387
|
+
rpc,
|
|
388
|
+
manualDrafts,
|
|
389
|
+
setProviderError,
|
|
390
|
+
refresh
|
|
391
|
+
]);
|
|
392
|
+
const logout = (0, react.useCallback)(async (provider, name) => {
|
|
393
|
+
if (rpc === void 0) return;
|
|
394
|
+
if (!window.confirm(t("logoutConfirm", { provider: name }))) return;
|
|
395
|
+
setProviderError(provider, void 0);
|
|
396
|
+
try {
|
|
397
|
+
await callSubscriptionsAuth(rpc, "logout", { provider });
|
|
398
|
+
} catch (error) {
|
|
399
|
+
setProviderError(provider, messageOf(error));
|
|
400
|
+
}
|
|
401
|
+
await refresh();
|
|
402
|
+
}, [
|
|
403
|
+
rpc,
|
|
404
|
+
t,
|
|
405
|
+
setProviderError,
|
|
406
|
+
refresh
|
|
407
|
+
]);
|
|
408
|
+
if (rpc === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
409
|
+
style: styles.intro,
|
|
410
|
+
children: t("unavailable")
|
|
411
|
+
});
|
|
412
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
413
|
+
style: styles.section,
|
|
414
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
415
|
+
style: styles.intro,
|
|
416
|
+
children: t("intro")
|
|
417
|
+
}), PROVIDERS.map(({ id, name }) => {
|
|
418
|
+
const status = statuses[id];
|
|
419
|
+
const busy = status?.busy === true;
|
|
420
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
421
|
+
style: styles.card,
|
|
422
|
+
children: [
|
|
423
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
424
|
+
style: styles.cardHeader,
|
|
425
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
426
|
+
...styles.dot,
|
|
427
|
+
background: dotColor(status)
|
|
428
|
+
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
429
|
+
style: styles.name,
|
|
430
|
+
children: name
|
|
431
|
+
})]
|
|
432
|
+
}),
|
|
433
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
434
|
+
style: styles.statusLine,
|
|
435
|
+
children: statusText(t, status)
|
|
436
|
+
}),
|
|
437
|
+
status?.detail !== void 0 && status.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
438
|
+
style: styles.statusLine,
|
|
439
|
+
children: status.detail
|
|
440
|
+
}),
|
|
441
|
+
errors[id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
442
|
+
style: styles.errorLine,
|
|
443
|
+
children: errors[id]
|
|
444
|
+
}),
|
|
445
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
446
|
+
style: styles.actions,
|
|
447
|
+
children: [
|
|
448
|
+
!busy && status?.loggedIn !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
449
|
+
type: "button",
|
|
450
|
+
style: styles.button,
|
|
451
|
+
onClick: () => {
|
|
452
|
+
login(id);
|
|
453
|
+
},
|
|
454
|
+
children: t("login")
|
|
455
|
+
}),
|
|
456
|
+
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
457
|
+
type: "button",
|
|
458
|
+
style: styles.button,
|
|
459
|
+
onClick: () => {
|
|
460
|
+
cancel(id);
|
|
461
|
+
},
|
|
462
|
+
children: t("cancel")
|
|
463
|
+
}),
|
|
464
|
+
status?.loggedIn === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
465
|
+
type: "button",
|
|
466
|
+
style: styles.button,
|
|
467
|
+
onClick: () => {
|
|
468
|
+
logout(id, name);
|
|
469
|
+
},
|
|
470
|
+
children: t("logout")
|
|
471
|
+
})
|
|
472
|
+
]
|
|
473
|
+
}),
|
|
474
|
+
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
475
|
+
style: styles.manual,
|
|
476
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("manualSummary") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
477
|
+
style: styles.manualRow,
|
|
478
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
479
|
+
style: styles.manualInput,
|
|
480
|
+
value: manualDrafts[id],
|
|
481
|
+
placeholder: t("manualPlaceholder"),
|
|
482
|
+
onChange: (event) => setManualDrafts((prev) => ({
|
|
483
|
+
...prev,
|
|
484
|
+
[id]: event.target.value
|
|
485
|
+
}))
|
|
486
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
487
|
+
type: "button",
|
|
488
|
+
style: styles.button,
|
|
489
|
+
onClick: () => {
|
|
490
|
+
submitManual(id);
|
|
491
|
+
},
|
|
492
|
+
children: t("submit")
|
|
493
|
+
})]
|
|
494
|
+
})]
|
|
495
|
+
})
|
|
496
|
+
]
|
|
497
|
+
}, id);
|
|
498
|
+
})]
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/client/index.ts
|
|
504
|
+
/** Dictionary namespace owned by this plugin. */
|
|
505
|
+
const NS = "settings.subscriptions";
|
|
506
|
+
/**
|
|
507
|
+
* Required services (cordis fiber inject): `slots` carries the registration
|
|
508
|
+
* seat, `connection` the `/subscriptions-auth` RPC caller, and `locale` the copy
|
|
509
|
+
* dictionaries.
|
|
510
|
+
*/
|
|
511
|
+
const inject = [
|
|
512
|
+
"slots",
|
|
513
|
+
"connection",
|
|
514
|
+
"locale"
|
|
515
|
+
];
|
|
516
|
+
/**
|
|
517
|
+
* Register the Subscriptions section once the `settings.section` declaration
|
|
518
|
+
* is on the ledger (the shell's apply order relative to this one is NOT
|
|
519
|
+
* constrained; registration depends on the slot through `slots.inject()`).
|
|
520
|
+
* @param ctx - client root context.
|
|
521
|
+
*/
|
|
522
|
+
function apply(ctx) {
|
|
523
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
524
|
+
zh,
|
|
525
|
+
en
|
|
526
|
+
}), "dsh-plugin-subscriptions: copy dictionaries");
|
|
527
|
+
const connection = ctx.get("connection");
|
|
528
|
+
const t = ctx.locale.bind(NS);
|
|
529
|
+
const injected = () => ({
|
|
530
|
+
rpc: connection.rpc,
|
|
531
|
+
t
|
|
532
|
+
});
|
|
533
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
534
|
+
name: "settings.section",
|
|
535
|
+
id: "subscriptions",
|
|
536
|
+
order: 90,
|
|
537
|
+
label: () => t("nav"),
|
|
538
|
+
inject: injected
|
|
539
|
+
}, SubscriptionsSection));
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
//#endregion
|
|
543
|
+
exports.apply = apply;
|
|
544
|
+
exports.inject = inject;
|
|
545
|
+
return module.exports; } });
|
|
546
|
+
//# sourceMappingURL=client.js.map
|