dsh-plugin-subscription-oauth 0.6.1 → 0.8.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/client.js CHANGED
@@ -2300,14 +2300,16 @@ function balanceOf(value) {
2300
2300
  * Format one wallet for the chip's compact line.
2301
2301
  *
2302
2302
  * A balance is money, not a percentage, so it renders as `¥6.89` rather than a meter;
2303
- * the currency prefix keeps two currencies from being mistaken for one sum.
2303
+ * the currency prefix keeps two currencies from being mistaken for one sum. The amount
2304
+ * is rounded to two decimal places (a balance reads to the cent, and a figure like
2305
+ * `1.1` for `1.10` would mislead) and always carries both places.
2304
2306
  *
2305
2307
  * @param wallet - the wallet to show.
2306
- * @returns the short form, e.g. `¥6.89` or `+¥2` for a bonus wallet.
2308
+ * @returns the short form, e.g. `¥6.89` or `+$2.00` for a bonus wallet.
2307
2309
  */
2308
2310
  function formatBalance(wallet) {
2309
2311
  const amount = Number.parseFloat(wallet.balance);
2310
- const trimmed = Number.isFinite(amount) ? String(Math.round(amount * 100) / 100) : wallet.balance;
2312
+ const trimmed = Number.isFinite(amount) ? amount.toFixed(2) : wallet.balance;
2311
2313
  const symbol = wallet.currency === "CNY" ? "¥" : wallet.currency === "USD" ? "$" : `${wallet.currency} `;
2312
2314
  return `${wallet.bonus ? "+" : ""}${symbol}${trimmed}`;
2313
2315
  }
@@ -2336,43 +2338,71 @@ function shortWindow(label) {
2336
2338
  return label.length <= 4 ? label : label.slice(0, 4);
2337
2339
  }
2338
2340
  /**
2339
- * Pick the window a single line should show.
2341
+ * Reduce a route's per-account usage to one reading per account.
2340
2342
  *
2341
- * A plan can meter several windows at once (a rolling five-hour one and a weekly one).
2342
- * The most constrained is the one that will stop work soonest, so it earns the space;
2343
- * ties fall to the earlier window, which is the order the providers state them in.
2343
+ * A provider can hold several accounts (two Antigravity sign-ins, say), and the chip
2344
+ * shows each one's allowance on its own line, the way the settings cards do — a merged
2345
+ * figure would hide that one account is exhausted while the other is free.
2344
2346
  *
2345
- * @param windows - the route's metered windows.
2346
- * @returns the window to show, or undefined when none states a figure.
2347
+ * @param usage - every account the route answered for.
2348
+ * @returns one reading per account that states a figure, in the route's order.
2347
2349
  */
2348
- function pickWindow(windows) {
2349
- let best;
2350
- for (const window$1 of windows) {
2351
- if (window$1.usedPercent === void 0) continue;
2352
- if (best?.usedPercent === void 0 || window$1.usedPercent > best.usedPercent) best = window$1;
2350
+ /**
2351
+ * Reduce a route's per-account usage to one reading per account.
2352
+ *
2353
+ * A provider can hold several accounts (two Antigravity sign-ins, say). Every account's
2354
+ * windows are kept as the route stated them — a percentage-metered quota and a credit
2355
+ * balance (`credits`) alike — so the per-account panel shows the real figures, and the
2356
+ * compact line's merge decides which to integrate.
2357
+ *
2358
+ * @param usage - every account the route answered for.
2359
+ * @returns one reading per account that states any window, in the route's order.
2360
+ */
2361
+ function accountReadingsOf(usage) {
2362
+ const out = [];
2363
+ for (const entry of usage) {
2364
+ if (entry.windows.length === 0) continue;
2365
+ out.push({
2366
+ account: entry.account,
2367
+ windows: entry.windows,
2368
+ ...entry.plan === void 0 ? {} : { plan: entry.plan }
2369
+ });
2353
2370
  }
2354
- return best;
2371
+ return out;
2355
2372
  }
2356
2373
  /**
2357
- * Reduce one route's usage to what the chip renders.
2374
+ * Merge every account's percentage-metered windows into one figure per label.
2375
+ *
2376
+ * The chip's compact line integrates the whole pool: two accounts at 50 percent read as
2377
+ * one window at 50 percent (the same allowance spent twice as wide), not two lines. A
2378
+ * credit balance (`credits`) is money, not a share of a shared allowance, so it is not
2379
+ * merged — it stays on the per-account panel, where it reads correctly. Only windows
2380
+ * that state a `usedPercent` (a share of an allowance) merge.
2358
2381
  *
2359
- * @param provider - the route the figures came from.
2360
- * @param label - the route's display name.
2361
- * @param usage - the accounts the route answered for.
2362
- * @returns the reading, or undefined when the route stated no figure.
2382
+ * @param readings - one per account, from {@link accountReadingsOf}.
2383
+ * @returns one merged window per percentage-metered label, most-spent first.
2363
2384
  */
2364
- function readingOf(provider, label, usage) {
2365
- for (const entry of usage) {
2366
- const window$1 = pickWindow(entry.windows);
2367
- if (window$1 === void 0) continue;
2368
- return {
2369
- provider,
2385
+ function mergeAccountWindows(readings) {
2386
+ const byLabel = /* @__PURE__ */ new Map();
2387
+ for (const reading of readings) for (const window$1 of reading.windows) {
2388
+ if (window$1.usedPercent === void 0) continue;
2389
+ const list = byLabel.get(window$1.label) ?? [];
2390
+ list.push(window$1);
2391
+ byLabel.set(window$1.label, list);
2392
+ }
2393
+ const merged = [];
2394
+ for (const [label, list] of byLabel) {
2395
+ const withPercent = list.filter((window$1) => window$1.usedPercent !== void 0);
2396
+ if (withPercent.length === 0) continue;
2397
+ const usedPercent = withPercent.reduce((sum, window$1) => sum + (window$1.usedPercent ?? 0), 0) / withPercent.length;
2398
+ const resets = list.map((window$1) => window$1.resetsAt).filter((at) => typeof at === "number");
2399
+ merged.push({
2370
2400
  label,
2371
- windows: entry.windows.filter((candidate) => candidate.usedPercent !== void 0),
2372
- window: window$1,
2373
- ...entry.plan === void 0 ? {} : { plan: entry.plan }
2374
- };
2401
+ usedPercent: Math.max(0, Math.min(100, usedPercent)),
2402
+ ...resets.length === 0 ? {} : { resetsAt: Math.min(...resets) }
2403
+ });
2375
2404
  }
2405
+ return merged.sort((left, right) => (right.usedPercent ?? 0) - (left.usedPercent ?? 0));
2376
2406
  }
2377
2407
  /**
2378
2408
  * Format a window's reset the way the settings cards do.
@@ -2402,6 +2432,7 @@ function formatReset(at, now) {
2402
2432
  */
2403
2433
  function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, useProjection }) {
2404
2434
  const [reading, setReading] = (0, react.useState)(void 0);
2435
+ const [accountReadings, setAccountReadings] = (0, react.useState)(void 0);
2405
2436
  const [balance, setBalance] = (0, react.useState)(void 0);
2406
2437
  const [ccBalance, setCcBalance] = (0, react.useState)(void 0);
2407
2438
  const [now, setNow] = (0, react.useState)(() => Date.now());
@@ -2438,8 +2469,7 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2438
2469
  ]);
2439
2470
  const load = (0, react.useCallback)(async (target) => {
2440
2471
  try {
2441
- const result = await call("usage", { provider: target });
2442
- return readingOf(target, result.status.providers.find((entry) => entry.provider === target)?.displayName ?? target, result.usage);
2472
+ return accountReadingsOf((await call("usage", { provider: target })).usage);
2443
2473
  } catch {
2444
2474
  return;
2445
2475
  }
@@ -2465,7 +2495,7 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2465
2495
  (0, react.useEffect)(() => {
2466
2496
  const target = selected !== void 0 && isOwnProvider(selected) ? selected : lastOwn.current;
2467
2497
  if (target === void 0) {
2468
- setReading(void 0);
2498
+ setAccountReadings(void 0);
2469
2499
  return;
2470
2500
  }
2471
2501
  let live = true;
@@ -2474,7 +2504,7 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2474
2504
  load(target).then((next) => {
2475
2505
  if (!live || next === void 0) return;
2476
2506
  lastOwn.current = target;
2477
- setReading(next);
2507
+ setAccountReadings(next);
2478
2508
  });
2479
2509
  };
2480
2510
  refresh();
@@ -2592,10 +2622,9 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2592
2622
  ]
2593
2623
  });
2594
2624
  }
2595
- if (reading === void 0) return null;
2596
- if (reading.window.usedPercent === void 0) return null;
2597
- formatReset(reading.window.resetsAt, now);
2598
- const short = shortLabel(reading.provider, reading.label);
2625
+ if (accountReadings === void 0 || accountReadings.length === 0) return null;
2626
+ const short = shortLabel(selected ?? "", selected ?? "");
2627
+ const merged = mergeAccountWindows(accountReadings);
2599
2628
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2600
2629
  className: "dsh-subscription-usage-chip",
2601
2630
  children: [
@@ -2604,7 +2633,7 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2604
2633
  type: "button",
2605
2634
  className: "dsh-subscription-usage-chip-trigger",
2606
2635
  "aria-expanded": open,
2607
- title: t("usageChipTitle", { provider: reading.label }),
2636
+ title: t("usageChipTitle", { provider: selected ?? "" }),
2608
2637
  onClick: (event) => {
2609
2638
  event.stopPropagation();
2610
2639
  setOpen((was) => !was);
@@ -2612,7 +2641,7 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2612
2641
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2613
2642
  className: "dsh-subscription-usage-chip-label",
2614
2643
  children: short
2615
- }), reading.windows.map((window$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2644
+ }), merged.map((window$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2616
2645
  className: "dsh-subscription-usage-chip-cell",
2617
2646
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2618
2647
  className: "dsh-subscription-usage-chip-window",
@@ -2626,22 +2655,26 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2626
2655
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2627
2656
  className: "dsh-subscription-usage-chip-panel",
2628
2657
  role: "status",
2629
- children: [
2630
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2631
- className: "dsh-subscription-usage-chip-full",
2632
- children: t("usageChipFull", { provider: reading.label })
2633
- }),
2634
- reading.windows.map((window$1) => {
2658
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2659
+ className: "dsh-subscription-usage-chip-full",
2660
+ children: t("usageChipFull", { provider: selected ?? "" })
2661
+ }), accountReadings.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2662
+ className: "dsh-subscription-usage-chip-account",
2663
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2664
+ className: "dsh-subscription-usage-chip-meterHead",
2665
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: account.plan ?? account.account })
2666
+ }), account.windows.map((window$1) => {
2635
2667
  const windowReset = formatReset(window$1.resetsAt, now);
2668
+ const amount = window$1.usedPercent === void 0 && window$1.used !== void 0 && window$1.limit !== void 0 ? `${String(window$1.used)}/${String(window$1.limit)}` : `${String(Math.round(window$1.usedPercent ?? 0))}%`;
2636
2669
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2637
2670
  className: "dsh-subscription-usage-chip-meter",
2638
2671
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2639
2672
  className: "dsh-subscription-usage-chip-meterHead",
2640
2673
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: window$1.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2641
2674
  className: "dsh-subscription-usage-chip-detail",
2642
- children: `${String(Math.round(window$1.usedPercent ?? 0))}%${windowReset === void 0 ? "" : ` · ${windowReset}`}`
2675
+ children: `${amount}${windowReset === void 0 ? "" : ` · ${windowReset}`}`
2643
2676
  })]
2644
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2677
+ }), window$1.usedPercent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2645
2678
  className: "dsh-subscription-usage-chip-track",
2646
2679
  "aria-hidden": "true",
2647
2680
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -2650,12 +2683,8 @@ function UsageChip({ call, t, isOwnProvider, accountBalance, ccSwitchBalance, us
2650
2683
  })
2651
2684
  })]
2652
2685
  }, window$1.label);
2653
- }),
2654
- reading.plan === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2655
- className: "dsh-subscription-usage-chip-detail",
2656
- children: reading.plan
2657
- })
2658
- ]
2686
+ })]
2687
+ }, account.account))]
2659
2688
  })
2660
2689
  ]
2661
2690
  });