dsh-plugin-subscription-oauth 0.5.0 → 0.6.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.
@@ -10,9 +10,6 @@
10
10
  import { RPC_PREFIX } from '../protocol.js';
11
11
  /** The shared channel the node half's routes are mounted on. */
12
12
  const CHANNEL = '/api';
13
- /** The host's own DeepSeek account controller, from which the chip reads a balance. */
14
- const ACCOUNT_CHANNEL = '/api';
15
- const ACCOUNT_NAMESPACE = 'account';
16
13
  /** A failed call: either the transport rejected or the endpoint answered `ok: false`. */
17
14
  export class SubscriptionsRpcError extends Error {
18
15
  code;
@@ -46,23 +43,25 @@ export function createSubscriptionsCall(rpc) {
46
43
  };
47
44
  }
48
45
  /**
49
- * Read the DeepSeek account's balance through the host's own account controller.
46
+ * Read the DeepSeek account's balance through the plugin's own RPC.
50
47
  *
51
- * The host owns the DeepSeek OAuth grant and the platform endpoint, so this asks it
52
- * rather than re-deriving either. It returns `null` when the account is signed out or
53
- * the read fails, so a chip can simply not render in that case.
48
+ * The browser cannot call the host's account service directly — it sits behind the
49
+ * host's Remote controller, not a `/api` fetch route — so the node half reads it and
50
+ * serves it on the plugin's channel, the same one the rest of the chip's data travels.
54
51
  *
55
52
  * @param rpc - the browser connection's RPC caller.
56
- * @returns the balance, or null when there is nothing to show.
53
+ * @returns the balance, or undefined when there is nothing to show.
57
54
  */
58
55
  export function createAccountBalanceCall(rpc) {
59
56
  return async function call() {
60
57
  let result;
61
58
  try {
62
- result = await rpc.call(ACCOUNT_CHANNEL, 'account/getBalance', {
59
+ result = await rpc.call(CHANNEL, `${RPC_PREFIX}deepseek-balance`, {
63
60
  version: '1',
64
61
  locale: Intl.DateTimeFormat().resolvedOptions().locale ?? 'en-US',
65
- timezoneOffsetSeconds: -new Date().getTimezoneOffset(),
62
+ // East-positive whole seconds, as the host's account service expects (the same
63
+ // value DSH's own account card sends).
64
+ timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60,
66
65
  });
67
66
  }
68
67
  catch (error) {
@@ -77,18 +76,21 @@ export function createAccountBalanceCall(rpc) {
77
76
  * Read a manually added provider's balance through the plugin's cc-switch endpoint.
78
77
  *
79
78
  * The caller names the credential reference the provider's profile uses (its
80
- * `apiKeyEnv`), and the host's credential service resolves it to the API key. The result
79
+ * Read a non-subscription provider's balance through the plugin's cc-switch endpoint.
80
+ *
81
+ * The caller names the provider; the server maps it to the credential reference the
82
+ * provider's profile names (`apiKeyEnv`) and resolves that to the API key. The result
81
83
  * is `undefined` when there is no key or the provider did not answer — never a wrong
82
84
  * figure.
83
85
  *
84
86
  * @param rpc - the browser connection's RPC caller.
85
- * @returns a narrow caller taking the credential reference.
87
+ * @returns a narrow caller taking the provider id.
86
88
  */
87
89
  export function createCcSwitchBalanceCall(rpc) {
88
- return async function call(credentialRef) {
90
+ return async function call(provider) {
89
91
  let result;
90
92
  try {
91
- result = await rpc.call(CHANNEL, `${RPC_PREFIX}ccswitch-balance`, { credentialRef });
93
+ result = await rpc.call(CHANNEL, `${RPC_PREFIX}ccswitch-balance`, { provider });
92
94
  }
93
95
  catch (error) {
94
96
  throw new SubscriptionsRpcError(messageOf(error), 'RPC_TRANSPORT');
package/lib/client.js CHANGED
@@ -632,8 +632,6 @@ 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";
637
635
  /** A failed call: either the transport rejected or the endpoint answered `ok: false`. */
638
636
  var SubscriptionsRpcError = class extends Error {
639
637
  constructor(message, code) {
@@ -664,23 +662,23 @@ function createSubscriptionsCall(rpc) {
664
662
  };
665
663
  }
666
664
  /**
667
- * Read the DeepSeek account's balance through the host's own account controller.
665
+ * Read the DeepSeek account's balance through the plugin's own RPC.
668
666
  *
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.
667
+ * The browser cannot call the host's account service directly — it sits behind the
668
+ * host's Remote controller, not a `/api` fetch route — so the node half reads it and
669
+ * serves it on the plugin's channel, the same one the rest of the chip's data travels.
672
670
  *
673
671
  * @param rpc - the browser connection's RPC caller.
674
- * @returns the balance, or null when there is nothing to show.
672
+ * @returns the balance, or undefined when there is nothing to show.
675
673
  */
676
674
  function createAccountBalanceCall(rpc) {
677
675
  return async function call() {
678
676
  let result;
679
677
  try {
680
- result = await rpc.call(ACCOUNT_CHANNEL, "account/getBalance", {
678
+ result = await rpc.call(CHANNEL, `${RPC_PREFIX}deepseek-balance`, {
681
679
  version: "1",
682
680
  locale: Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US",
683
- timezoneOffsetSeconds: -(/* @__PURE__ */ new Date()).getTimezoneOffset()
681
+ timezoneOffsetSeconds: -(/* @__PURE__ */ new Date()).getTimezoneOffset() * 60
684
682
  });
685
683
  } catch (error) {
686
684
  throw new SubscriptionsRpcError(messageOf$1(error), "RPC_TRANSPORT");
@@ -693,18 +691,21 @@ function createAccountBalanceCall(rpc) {
693
691
  * Read a manually added provider's balance through the plugin's cc-switch endpoint.
694
692
  *
695
693
  * 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
694
+ * Read a non-subscription provider's balance through the plugin's cc-switch endpoint.
695
+ *
696
+ * The caller names the provider; the server maps it to the credential reference the
697
+ * provider's profile names (`apiKeyEnv`) and resolves that to the API key. The result
697
698
  * is `undefined` when there is no key or the provider did not answer — never a wrong
698
699
  * figure.
699
700
  *
700
701
  * @param rpc - the browser connection's RPC caller.
701
- * @returns a narrow caller taking the credential reference.
702
+ * @returns a narrow caller taking the provider id.
702
703
  */
703
704
  function createCcSwitchBalanceCall(rpc) {
704
- return async function call(credentialRef) {
705
+ return async function call(provider) {
705
706
  let result;
706
707
  try {
707
- result = await rpc.call(CHANNEL, `${RPC_PREFIX}ccswitch-balance`, { credentialRef });
708
+ result = await rpc.call(CHANNEL, `${RPC_PREFIX}ccswitch-balance`, { provider });
708
709
  } catch (error) {
709
710
  throw new SubscriptionsRpcError(messageOf$1(error), "RPC_TRANSPORT");
710
711
  }
@@ -2272,21 +2273,23 @@ function providerOf(projection) {
2272
2273
  return (projection?.next ?? projection?.lastUsed)?.provider;
2273
2274
  }
2274
2275
  /**
2275
- * Reduce the host's account-balance payload to the wallets the chip shows.
2276
+ * Reduce the node half's balance payload to the wallets the chip shows.
2276
2277
  *
2277
2278
  * 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
+ * first and any promotional credit as a secondary figure. A bonus wallet that holds
2280
+ * nothing is dropped rather than shown as `+¥0`: the platform reports one for every
2281
+ * currency whether or not there is a bonus, and an empty one is noise, not information.
2279
2282
  *
2280
- * @param value - the host's ready balance, or undefined when there is none.
2283
+ * @param value - the node half's balance, or undefined when there is none.
2281
2284
  * @returns the wallets, topped-up first then bonus, or undefined when nothing is usable.
2282
2285
  */
2283
2286
  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) => ({
2287
+ if (value === null || value === void 0) return void 0;
2288
+ const wallets = [...(value.wallets ?? []).map((wallet) => ({
2286
2289
  currency: wallet.currency,
2287
2290
  balance: wallet.balance,
2288
2291
  bonus: false
2289
- })), ...(value.bonusWallets ?? []).map((wallet) => ({
2292
+ })), ...(value.bonusWallets ?? []).filter((wallet) => Number.parseFloat(wallet.balance) > 0).map((wallet) => ({
2290
2293
  currency: wallet.currency,
2291
2294
  balance: wallet.balance,
2292
2295
  bonus: true
@@ -2397,7 +2400,7 @@ function formatReset(at, now) {
2397
2400
  * @param props - injected RPC caller, copy, and the session's provider reader.
2398
2401
  * @returns the chip, or null when there is nothing true to say.
2399
2402
  */
2400
- function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, ccSwitchCredentialRef, useProjection }) {
2403
+ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, useProjection }) {
2401
2404
  const [reading, setReading] = (0, react.useState)(void 0);
2402
2405
  const [balance, setBalance] = (0, react.useState)(void 0);
2403
2406
  const [ccBalance, setCcBalance] = (0, react.useState)(void 0);
@@ -2406,15 +2409,16 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, cc
2406
2409
  const lastOwn = (0, react.useRef)(void 0);
2407
2410
  const selected = useProjection("modelSelection", (value) => providerOf(value));
2408
2411
  const isDeepSeek = selected === "deepseek-account";
2409
- const ccRef = selected === void 0 || isOwnProvider(selected) || isDeepSeek ? void 0 : ccSwitchCredentialRef(selected);
2412
+ const isBalanceProvider = selected !== void 0 && !isOwnProvider(selected) && !isDeepSeek;
2410
2413
  (0, react.useEffect)(() => {
2411
- if (ccRef === void 0) {
2414
+ if (!isBalanceProvider || selected === void 0) {
2412
2415
  setCcBalance(void 0);
2413
2416
  return;
2414
2417
  }
2415
2418
  let live = true;
2419
+ const target = selected;
2416
2420
  const refresh = () => {
2417
- ccSwitchBalance(ccRef).then((value) => {
2421
+ ccSwitchBalance(target).then((value) => {
2418
2422
  if (live) setCcBalance(value === void 0 ? void 0 : {
2419
2423
  currency: value.currency,
2420
2424
  total: value.total
@@ -2427,7 +2431,11 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, cc
2427
2431
  live = false;
2428
2432
  clearInterval(timer);
2429
2433
  };
2430
- }, [ccRef, ccSwitchBalance]);
2434
+ }, [
2435
+ isBalanceProvider,
2436
+ selected,
2437
+ ccSwitchBalance
2438
+ ]);
2431
2439
  const load = (0, react.useCallback)(async (target) => {
2432
2440
  try {
2433
2441
  const result = await call("usage", { provider: target });
@@ -2490,7 +2498,7 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, cc
2490
2498
  document.removeEventListener("click", close);
2491
2499
  };
2492
2500
  }, [open]);
2493
- if (ccRef !== void 0) {
2501
+ if (isBalanceProvider) {
2494
2502
  if (ccBalance === void 0) return null;
2495
2503
  const short$1 = shortLabel(selected ?? "", selected ?? "");
2496
2504
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -2680,17 +2688,6 @@ const SECTION_ORDER = 92;
2680
2688
  */
2681
2689
  const CHIP_ORDER = 10;
2682
2690
  /**
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
- /**
2694
2691
  * Required services: `slots` carries the registration seat, `connection` the
2695
2692
  * RPC caller, and `locale` the copy dictionaries.
2696
2693
  */
@@ -2734,8 +2731,7 @@ function apply(ctx) {
2734
2731
  t,
2735
2732
  isOwnProvider: (provider) => OWN_PROVIDER_IDS.has(provider),
2736
2733
  accountBalance: createAccountBalanceCall(connection.rpc),
2737
- ccSwitchBalance: createCcSwitchBalanceCall(connection.rpc),
2738
- ccSwitchCredentialRef: (provider) => CC_SWITCH_CREDENTIAL_REFS[provider]
2734
+ ccSwitchBalance: createCcSwitchBalanceCall(connection.rpc)
2739
2735
  })
2740
2736
  }, UsageChip));
2741
2737
  }