dsh-plugin-subscription-oauth 0.4.0 → 0.5.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/lib/auth/ccswitch.d.ts +36 -0
- package/lib/auth/ccswitch.js +75 -0
- package/lib/auth/rpc.d.ts +8 -1
- package/lib/auth/rpc.js +29 -17
- package/lib/client/UsageChip.d.ts +70 -2
- package/lib/client/UsageChip.js +116 -1
- package/lib/client/index.d.ts +1 -1
- package/lib/client/index.js +17 -1
- package/lib/client/subscriptions-rpc.d.ts +53 -1
- package/lib/client/subscriptions-rpc.js +56 -0
- package/lib/client.js +241 -2
- package/lib/client.js.map +1 -1
- package/lib/protocol.d.ts +50 -1
- package/lib/protocol.js +1 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -632,6 +632,8 @@ const RPC_PREFIX = "subscription-oauth.";
|
|
|
632
632
|
//#region src/client/subscriptions-rpc.ts
|
|
633
633
|
/** The shared channel the node half's routes are mounted on. */
|
|
634
634
|
const CHANNEL = "/api";
|
|
635
|
+
/** The host's own DeepSeek account controller, from which the chip reads a balance. */
|
|
636
|
+
const ACCOUNT_CHANNEL = "/api";
|
|
635
637
|
/** A failed call: either the transport rejected or the endpoint answered `ok: false`. */
|
|
636
638
|
var SubscriptionsRpcError = class extends Error {
|
|
637
639
|
constructor(message, code) {
|
|
@@ -661,6 +663,55 @@ function createSubscriptionsCall(rpc) {
|
|
|
661
663
|
return result.value;
|
|
662
664
|
};
|
|
663
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Read the DeepSeek account's balance through the host's own account controller.
|
|
668
|
+
*
|
|
669
|
+
* The host owns the DeepSeek OAuth grant and the platform endpoint, so this asks it
|
|
670
|
+
* rather than re-deriving either. It returns `null` when the account is signed out or
|
|
671
|
+
* the read fails, so a chip can simply not render in that case.
|
|
672
|
+
*
|
|
673
|
+
* @param rpc - the browser connection's RPC caller.
|
|
674
|
+
* @returns the balance, or null when there is nothing to show.
|
|
675
|
+
*/
|
|
676
|
+
function createAccountBalanceCall(rpc) {
|
|
677
|
+
return async function call() {
|
|
678
|
+
let result;
|
|
679
|
+
try {
|
|
680
|
+
result = await rpc.call(ACCOUNT_CHANNEL, "account/getBalance", {
|
|
681
|
+
version: "1",
|
|
682
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US",
|
|
683
|
+
timezoneOffsetSeconds: -(/* @__PURE__ */ new Date()).getTimezoneOffset()
|
|
684
|
+
});
|
|
685
|
+
} catch (error) {
|
|
686
|
+
throw new SubscriptionsRpcError(messageOf$1(error), "RPC_TRANSPORT");
|
|
687
|
+
}
|
|
688
|
+
if (!result.ok) throw new SubscriptionsRpcError(result.error.message, result.error.code);
|
|
689
|
+
return result.value;
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Read a manually added provider's balance through the plugin's cc-switch endpoint.
|
|
694
|
+
*
|
|
695
|
+
* The caller names the credential reference the provider's profile uses (its
|
|
696
|
+
* `apiKeyEnv`), and the host's credential service resolves it to the API key. The result
|
|
697
|
+
* is `undefined` when there is no key or the provider did not answer — never a wrong
|
|
698
|
+
* figure.
|
|
699
|
+
*
|
|
700
|
+
* @param rpc - the browser connection's RPC caller.
|
|
701
|
+
* @returns a narrow caller taking the credential reference.
|
|
702
|
+
*/
|
|
703
|
+
function createCcSwitchBalanceCall(rpc) {
|
|
704
|
+
return async function call(credentialRef) {
|
|
705
|
+
let result;
|
|
706
|
+
try {
|
|
707
|
+
result = await rpc.call(CHANNEL, `${RPC_PREFIX}ccswitch-balance`, { credentialRef });
|
|
708
|
+
} catch (error) {
|
|
709
|
+
throw new SubscriptionsRpcError(messageOf$1(error), "RPC_TRANSPORT");
|
|
710
|
+
}
|
|
711
|
+
if (!result.ok) throw new SubscriptionsRpcError(result.error.message, result.error.code);
|
|
712
|
+
return result.value;
|
|
713
|
+
};
|
|
714
|
+
}
|
|
664
715
|
|
|
665
716
|
//#endregion
|
|
666
717
|
//#region src/client/SubscriptionsSection.tsx
|
|
@@ -2221,6 +2272,43 @@ function providerOf(projection) {
|
|
|
2221
2272
|
return (projection?.next ?? projection?.lastUsed)?.provider;
|
|
2222
2273
|
}
|
|
2223
2274
|
/**
|
|
2275
|
+
* Reduce the host's account-balance payload to the wallets the chip shows.
|
|
2276
|
+
*
|
|
2277
|
+
* Bonus wallets are shown after the topped-up ones, so a reader sees their own credit
|
|
2278
|
+
* first and any promotional credit as a secondary figure.
|
|
2279
|
+
*
|
|
2280
|
+
* @param value - the host's ready balance, or undefined when there is none.
|
|
2281
|
+
* @returns the wallets, topped-up first then bonus, or undefined when nothing is usable.
|
|
2282
|
+
*/
|
|
2283
|
+
function balanceOf(value) {
|
|
2284
|
+
if (value === null || value === void 0 || !("value" in value) || value.value === void 0) return void 0;
|
|
2285
|
+
const wallets = [...value.value.map((wallet) => ({
|
|
2286
|
+
currency: wallet.currency,
|
|
2287
|
+
balance: wallet.balance,
|
|
2288
|
+
bonus: false
|
|
2289
|
+
})), ...(value.bonusWallets ?? []).map((wallet) => ({
|
|
2290
|
+
currency: wallet.currency,
|
|
2291
|
+
balance: wallet.balance,
|
|
2292
|
+
bonus: true
|
|
2293
|
+
}))];
|
|
2294
|
+
return wallets.length === 0 ? void 0 : wallets;
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Format one wallet for the chip's compact line.
|
|
2298
|
+
*
|
|
2299
|
+
* A balance is money, not a percentage, so it renders as `¥6.89` rather than a meter;
|
|
2300
|
+
* the currency prefix keeps two currencies from being mistaken for one sum.
|
|
2301
|
+
*
|
|
2302
|
+
* @param wallet - the wallet to show.
|
|
2303
|
+
* @returns the short form, e.g. `¥6.89` or `+¥2` for a bonus wallet.
|
|
2304
|
+
*/
|
|
2305
|
+
function formatBalance(wallet) {
|
|
2306
|
+
const amount = Number.parseFloat(wallet.balance);
|
|
2307
|
+
const trimmed = Number.isFinite(amount) ? String(Math.round(amount * 100) / 100) : wallet.balance;
|
|
2308
|
+
const symbol = wallet.currency === "CNY" ? "¥" : wallet.currency === "USD" ? "$" : `${wallet.currency} `;
|
|
2309
|
+
return `${wallet.bonus ? "+" : ""}${symbol}${trimmed}`;
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2224
2312
|
* The short form of a route's name.
|
|
2225
2313
|
*
|
|
2226
2314
|
* @param provider - the route id.
|
|
@@ -2309,12 +2397,37 @@ function formatReset(at, now) {
|
|
|
2309
2397
|
* @param props - injected RPC caller, copy, and the session's provider reader.
|
|
2310
2398
|
* @returns the chip, or null when there is nothing true to say.
|
|
2311
2399
|
*/
|
|
2312
|
-
function UsageChip({ call, t, isOwnProvider, useProjection }) {
|
|
2400
|
+
function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, ccSwitchCredentialRef, useProjection }) {
|
|
2313
2401
|
const [reading, setReading] = (0, react.useState)(void 0);
|
|
2402
|
+
const [balance, setBalance] = (0, react.useState)(void 0);
|
|
2403
|
+
const [ccBalance, setCcBalance] = (0, react.useState)(void 0);
|
|
2314
2404
|
const [now, setNow] = (0, react.useState)(() => Date.now());
|
|
2315
2405
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2316
2406
|
const lastOwn = (0, react.useRef)(void 0);
|
|
2317
2407
|
const selected = useProjection("modelSelection", (value) => providerOf(value));
|
|
2408
|
+
const isDeepSeek = selected === "deepseek-account";
|
|
2409
|
+
const ccRef = selected === void 0 || isOwnProvider(selected) || isDeepSeek ? void 0 : ccSwitchCredentialRef(selected);
|
|
2410
|
+
(0, react.useEffect)(() => {
|
|
2411
|
+
if (ccRef === void 0) {
|
|
2412
|
+
setCcBalance(void 0);
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
let live = true;
|
|
2416
|
+
const refresh = () => {
|
|
2417
|
+
ccSwitchBalance(ccRef).then((value) => {
|
|
2418
|
+
if (live) setCcBalance(value === void 0 ? void 0 : {
|
|
2419
|
+
currency: value.currency,
|
|
2420
|
+
total: value.total
|
|
2421
|
+
});
|
|
2422
|
+
}).catch(() => {});
|
|
2423
|
+
};
|
|
2424
|
+
refresh();
|
|
2425
|
+
const timer = setInterval(refresh, REFRESH_INTERVAL_MS);
|
|
2426
|
+
return () => {
|
|
2427
|
+
live = false;
|
|
2428
|
+
clearInterval(timer);
|
|
2429
|
+
};
|
|
2430
|
+
}, [ccRef, ccSwitchBalance]);
|
|
2318
2431
|
const load = (0, react.useCallback)(async (target) => {
|
|
2319
2432
|
try {
|
|
2320
2433
|
const result = await call("usage", { provider: target });
|
|
@@ -2323,6 +2436,24 @@ function UsageChip({ call, t, isOwnProvider, useProjection }) {
|
|
|
2323
2436
|
return;
|
|
2324
2437
|
}
|
|
2325
2438
|
}, [call]);
|
|
2439
|
+
(0, react.useEffect)(() => {
|
|
2440
|
+
if (!isDeepSeek) {
|
|
2441
|
+
setBalance(void 0);
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
let live = true;
|
|
2445
|
+
const refresh = () => {
|
|
2446
|
+
accountBalance().then((value) => {
|
|
2447
|
+
if (live) setBalance(balanceOf(value));
|
|
2448
|
+
}).catch(() => {});
|
|
2449
|
+
};
|
|
2450
|
+
refresh();
|
|
2451
|
+
const timer = setInterval(refresh, REFRESH_INTERVAL_MS);
|
|
2452
|
+
return () => {
|
|
2453
|
+
live = false;
|
|
2454
|
+
clearInterval(timer);
|
|
2455
|
+
};
|
|
2456
|
+
}, [isDeepSeek, accountBalance]);
|
|
2326
2457
|
(0, react.useEffect)(() => {
|
|
2327
2458
|
const target = selected !== void 0 && isOwnProvider(selected) ? selected : lastOwn.current;
|
|
2328
2459
|
if (target === void 0) {
|
|
@@ -2359,6 +2490,100 @@ function UsageChip({ call, t, isOwnProvider, useProjection }) {
|
|
|
2359
2490
|
document.removeEventListener("click", close);
|
|
2360
2491
|
};
|
|
2361
2492
|
}, [open]);
|
|
2493
|
+
if (ccRef !== void 0) {
|
|
2494
|
+
if (ccBalance === void 0) return null;
|
|
2495
|
+
const short$1 = shortLabel(selected ?? "", selected ?? "");
|
|
2496
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2497
|
+
className: "dsh-subscription-usage-chip",
|
|
2498
|
+
children: [
|
|
2499
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: usageChipCss }),
|
|
2500
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2501
|
+
type: "button",
|
|
2502
|
+
className: "dsh-subscription-usage-chip-trigger",
|
|
2503
|
+
"aria-expanded": open,
|
|
2504
|
+
title: t("usageChipTitle", { provider: selected ?? "" }),
|
|
2505
|
+
onClick: (event) => {
|
|
2506
|
+
event.stopPropagation();
|
|
2507
|
+
setOpen((was) => !was);
|
|
2508
|
+
},
|
|
2509
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2510
|
+
className: "dsh-subscription-usage-chip-label",
|
|
2511
|
+
children: short$1
|
|
2512
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2513
|
+
className: "dsh-subscription-usage-chip-cell",
|
|
2514
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2515
|
+
className: "dsh-subscription-usage-chip-value",
|
|
2516
|
+
children: `${ccBalance.currency === "CNY" ? "¥" : ccBalance.currency === "USD" ? "$" : `${ccBalance.currency} `}${ccBalance.total}`
|
|
2517
|
+
})
|
|
2518
|
+
})]
|
|
2519
|
+
}),
|
|
2520
|
+
open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2521
|
+
className: "dsh-subscription-usage-chip-panel",
|
|
2522
|
+
role: "status",
|
|
2523
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2524
|
+
className: "dsh-subscription-usage-chip-full",
|
|
2525
|
+
children: t("usageChipFull", { provider: selected ?? "" })
|
|
2526
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2527
|
+
className: "dsh-subscription-usage-chip-meter",
|
|
2528
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2529
|
+
className: "dsh-subscription-usage-chip-meterHead",
|
|
2530
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: ccBalance.currency }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2531
|
+
className: "dsh-subscription-usage-chip-detail",
|
|
2532
|
+
children: ccBalance.total
|
|
2533
|
+
})]
|
|
2534
|
+
})
|
|
2535
|
+
})]
|
|
2536
|
+
})
|
|
2537
|
+
]
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
if (isDeepSeek) {
|
|
2541
|
+
if (balance === void 0) return null;
|
|
2542
|
+
const short$1 = shortLabel("deepseek-account", "DeepSeek Account");
|
|
2543
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2544
|
+
className: "dsh-subscription-usage-chip",
|
|
2545
|
+
children: [
|
|
2546
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: usageChipCss }),
|
|
2547
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2548
|
+
type: "button",
|
|
2549
|
+
className: "dsh-subscription-usage-chip-trigger",
|
|
2550
|
+
"aria-expanded": open,
|
|
2551
|
+
title: t("usageChipTitle", { provider: "DeepSeek Account" }),
|
|
2552
|
+
onClick: (event) => {
|
|
2553
|
+
event.stopPropagation();
|
|
2554
|
+
setOpen((was) => !was);
|
|
2555
|
+
},
|
|
2556
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2557
|
+
className: "dsh-subscription-usage-chip-label",
|
|
2558
|
+
children: short$1
|
|
2559
|
+
}), balance.map((wallet) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2560
|
+
className: "dsh-subscription-usage-chip-cell",
|
|
2561
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2562
|
+
className: "dsh-subscription-usage-chip-value",
|
|
2563
|
+
children: formatBalance(wallet)
|
|
2564
|
+
})
|
|
2565
|
+
}, `${wallet.currency}${wallet.bonus ? ":bonus" : ""}`))]
|
|
2566
|
+
}),
|
|
2567
|
+
open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2568
|
+
className: "dsh-subscription-usage-chip-panel",
|
|
2569
|
+
role: "status",
|
|
2570
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2571
|
+
className: "dsh-subscription-usage-chip-full",
|
|
2572
|
+
children: t("usageChipFull", { provider: "DeepSeek Account" })
|
|
2573
|
+
}), balance.map((wallet) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2574
|
+
className: "dsh-subscription-usage-chip-meter",
|
|
2575
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2576
|
+
className: "dsh-subscription-usage-chip-meterHead",
|
|
2577
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: `${wallet.bonus ? "Bonus · " : ""}${wallet.currency}` }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2578
|
+
className: "dsh-subscription-usage-chip-detail",
|
|
2579
|
+
children: formatBalance(wallet)
|
|
2580
|
+
})]
|
|
2581
|
+
})
|
|
2582
|
+
}, `${wallet.currency}${wallet.bonus ? ":bonus" : ""}`))]
|
|
2583
|
+
})
|
|
2584
|
+
]
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2362
2587
|
if (reading === void 0) return null;
|
|
2363
2588
|
if (reading.window.usedPercent === void 0) return null;
|
|
2364
2589
|
formatReset(reading.window.resetsAt, now);
|
|
@@ -2455,6 +2680,17 @@ const SECTION_ORDER = 92;
|
|
|
2455
2680
|
*/
|
|
2456
2681
|
const CHIP_ORDER = 10;
|
|
2457
2682
|
/**
|
|
2683
|
+
* The credential reference a manually added provider's balance query uses, by provider
|
|
2684
|
+
* id.
|
|
2685
|
+
*
|
|
2686
|
+
* A manually added provider is not one this plugin serves, so the only way to read its
|
|
2687
|
+
* balance is cc-switch's query, which needs the provider's API key. The provider's
|
|
2688
|
+
* profile names that key by reference (`apiKeyEnv`), and the host's credential service
|
|
2689
|
+
* resolves the name; this map is the one place a provider id is tied to that name. A
|
|
2690
|
+
* provider with no entry gets no balance.
|
|
2691
|
+
*/
|
|
2692
|
+
const CC_SWITCH_CREDENTIAL_REFS = { huanling: "HUANLING_API_KEY" };
|
|
2693
|
+
/**
|
|
2458
2694
|
* Required services: `slots` carries the registration seat, `connection` the
|
|
2459
2695
|
* RPC caller, and `locale` the copy dictionaries.
|
|
2460
2696
|
*/
|
|
@@ -2496,7 +2732,10 @@ function apply(ctx) {
|
|
|
2496
2732
|
inject: () => ({
|
|
2497
2733
|
call,
|
|
2498
2734
|
t,
|
|
2499
|
-
isOwnProvider: (provider) => OWN_PROVIDER_IDS.has(provider)
|
|
2735
|
+
isOwnProvider: (provider) => OWN_PROVIDER_IDS.has(provider),
|
|
2736
|
+
accountBalance: createAccountBalanceCall(connection.rpc),
|
|
2737
|
+
ccSwitchBalance: createCcSwitchBalanceCall(connection.rpc),
|
|
2738
|
+
ccSwitchCredentialRef: (provider) => CC_SWITCH_CREDENTIAL_REFS[provider]
|
|
2500
2739
|
})
|
|
2501
2740
|
}, UsageChip));
|
|
2502
2741
|
}
|