dsh-ds-balance 2.1.0 → 2.1.2
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 +5 -1
- package/README_en.md +5 -1
- package/lib/adapters/console-logger.js +12 -4
- package/lib/adapters/domain-core-store.js +9 -3
- package/lib/adapters/http-deepseek-client.js +14 -4
- package/lib/adapters/salt-file.js +6 -2
- package/lib/client.js +410 -196
- package/lib/config.js +7 -1
- package/lib/domain/errors.js +10 -5
- package/lib/domain/select.js +5 -5
- package/lib/http/handlers.js +55 -13
- package/lib/index.js +34 -9
- package/lib/services/balance-service.js +13 -5
- package/lib/services/scheduler.js +6 -2
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/settings/fields.d.ts +3 -3
- package/lib/types/client/sidebar/BalancePopover.d.ts +16 -3
- package/lib/types/client/sidebar/PercentRing.d.ts +1 -1
- package/lib/types/client/sidebar/SidebarBalance.d.ts +6 -4
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="icon.svg" alt="dsh-ds-balance" width="80" height="80">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
<p align="center">
|
|
2
6
|
<h1 align="center">dsh-ds-balance</h1>
|
|
3
7
|
</p>
|
|
@@ -40,7 +44,7 @@
|
|
|
40
44
|
| 界面 | 一句话 | 你会用它来 |
|
|
41
45
|
|---|---|---|
|
|
42
46
|
| 侧栏圆环 | 常驻左边栏底部的状态环 + 名称,**展开态悬浮条目就报出余额金额**,**环里填了多少 = 余额离该币种预警线还有多远** | 扫一眼就知道还剩多少、离预警线多远 |
|
|
43
|
-
| 余额浮层 | 点条目展开:总额、赠送 /
|
|
47
|
+
| 余额浮层 | 点条目展开:总额、赠送 / 充值拆分、数据新鲜度、手动刷新(带冷却);**右上角图标跳到本插件的配置格**(宿主没有那条跨插件深链服务时退回 Plugins 列表,悬浮文字同步改口径) | 核对具体数字,以及数据是几分钟前的 |
|
|
44
48
|
| 设置卡片 | 连接 / 展示 / 阈值 / 刷新四组,**默认四组全收起**,点组头逐组展开 | 换端点、换币种、调预警线、调节奏 |
|
|
45
49
|
|
|
46
50
|
三种界面的分工是死的:**圆环回答「大概还剩多少」,浮层回答「具体是多少」,卡片回答「怎么算」。**
|
package/README_en.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="icon.svg" alt="dsh-ds-balance" width="80" height="80">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
<p align="center">
|
|
2
6
|
<h1 align="center">dsh-ds-balance</h1>
|
|
3
7
|
</p>
|
|
@@ -40,7 +44,7 @@ The balance needs somewhere to live that does not take up room. A permanent stat
|
|
|
40
44
|
| Surface | One line | Use it for |
|
|
41
45
|
|---|---|---|
|
|
42
46
|
| Sidebar ring | A status ring plus label at the bottom of the sidebar; **in the expanded state, hovering the entry reports the balance amount**, and **how full it is = how far the balance is from that currency's warning line** | Seeing at a glance how much is left and how close it is to the warning line |
|
|
43
|
-
| Balance popover | Click the entry: total, granted / topped-up split, data freshness, manual refresh (with cooldown); **an icon at its top right
|
|
47
|
+
| Balance popover | Click the entry: total, granted / topped-up split, data freshness, manual refresh (with cooldown); **an icon at its top right opens this plugin's configuration page** (on a host without that cross-plugin deep link it falls back to the Plugins list, and its tooltip says so) | Checking the exact figures, and how many minutes old they are |
|
|
44
48
|
| Settings card | Connection / Display / Thresholds / Refresh, **all collapsed by default**, expand a group from its header | Changing the endpoint, the currency, the warning lines, the cadence |
|
|
45
49
|
|
|
46
50
|
The division of labour is fixed: **the ring answers "roughly how much is left", the popover answers "exactly how much", and the card answers "how is that computed".**
|
|
@@ -26,10 +26,18 @@ function render(fields) {
|
|
|
26
26
|
*/
|
|
27
27
|
export function createConsoleLogger(sink = console) {
|
|
28
28
|
return {
|
|
29
|
-
debug: (message, fields) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
debug: (message, fields) => {
|
|
30
|
+
sink.debug(`${LOG_PREFIX} ${message}${render(fields)}`);
|
|
31
|
+
},
|
|
32
|
+
info: (message, fields) => {
|
|
33
|
+
sink.info(`${LOG_PREFIX} ${message}${render(fields)}`);
|
|
34
|
+
},
|
|
35
|
+
warn: (message, fields) => {
|
|
36
|
+
sink.warn(`${LOG_PREFIX} ${message}${render(fields)}`);
|
|
37
|
+
},
|
|
38
|
+
error: (message, fields) => {
|
|
39
|
+
sink.error(`${LOG_PREFIX} ${message}${render(fields)}`);
|
|
40
|
+
},
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
43
|
//# sourceMappingURL=console-logger.js.map
|
|
@@ -111,7 +111,9 @@ export class DomainCoreStore {
|
|
|
111
111
|
this.ready = null;
|
|
112
112
|
if (!this.openFailureLogged) {
|
|
113
113
|
this.openFailureLogged = true;
|
|
114
|
-
this.options.logger?.error('ds-balance: storage domain unavailable, running degraded', {
|
|
114
|
+
this.options.logger?.error('ds-balance: storage domain unavailable, running degraded', {
|
|
115
|
+
error: describeError(error),
|
|
116
|
+
});
|
|
115
117
|
}
|
|
116
118
|
});
|
|
117
119
|
this.ready = attempt;
|
|
@@ -128,7 +130,9 @@ export class DomainCoreStore {
|
|
|
128
130
|
async requireTable() {
|
|
129
131
|
await this.ensureOpen();
|
|
130
132
|
if (this.openError !== undefined) {
|
|
131
|
-
throw new StorageError(`storage domain unavailable: ${describeError(this.openError)}`, {
|
|
133
|
+
throw new StorageError(`storage domain unavailable: ${describeError(this.openError)}`, {
|
|
134
|
+
cause: this.openError,
|
|
135
|
+
});
|
|
132
136
|
}
|
|
133
137
|
if (this.table === null)
|
|
134
138
|
throw new StorageError('storage domain is not open');
|
|
@@ -160,7 +164,9 @@ export class DomainCoreStore {
|
|
|
160
164
|
if (this.closed)
|
|
161
165
|
return { ok: false, detail: 'closed' };
|
|
162
166
|
await this.ensureOpen();
|
|
163
|
-
return this.openError === undefined
|
|
167
|
+
return this.openError === undefined
|
|
168
|
+
? { ok: true }
|
|
169
|
+
: { ok: false, detail: describeError(this.openError) };
|
|
164
170
|
}
|
|
165
171
|
/** 幂等关闭。**必须挂在 `ctx.effect` 的 disposer 上。** */
|
|
166
172
|
async close() {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* DeepSeek 上游的 HTTP 实现。
|
|
3
3
|
* @module dsh-ds-balance/adapters/http-deepseek-client
|
|
4
4
|
*/
|
|
5
|
-
import { NetworkError, ParseError, TimeoutError, UpstreamError, classify, describeError } from '../domain/errors.js';
|
|
5
|
+
import { NetworkError, ParseError, TimeoutError, UpstreamError, classify, describeError, } from '../domain/errors.js';
|
|
6
6
|
import { parseErrorBody } from '../domain/normalize.js';
|
|
7
7
|
/** 去掉尾部斜杠,避免拼出双斜杠路径。 */
|
|
8
8
|
function joinUrl(baseUrl, path) {
|
|
@@ -39,13 +39,21 @@ export class HttpDeepSeekClient {
|
|
|
39
39
|
const raw = await this.fetchBalance(options);
|
|
40
40
|
const latencyMs = this.now() - startedAt;
|
|
41
41
|
const balances = Array.isArray(raw?.balance_infos)
|
|
42
|
-
? raw.balance_infos.map((item) => ({
|
|
42
|
+
? raw.balance_infos.map((item) => ({
|
|
43
|
+
currency: String(item.currency),
|
|
44
|
+
total: String(item.total_balance),
|
|
45
|
+
}))
|
|
43
46
|
: [];
|
|
44
47
|
return { ok: true, latencyMs, isAvailable: raw?.is_available === true, balances };
|
|
45
48
|
}
|
|
46
49
|
catch (error) {
|
|
47
50
|
const info = classify(error);
|
|
48
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
latencyMs: this.now() - startedAt,
|
|
54
|
+
code: info.code,
|
|
55
|
+
message: info.message,
|
|
56
|
+
};
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
/**
|
|
@@ -61,7 +69,9 @@ export class HttpDeepSeekClient {
|
|
|
61
69
|
timedOut = true;
|
|
62
70
|
controller.abort();
|
|
63
71
|
}, options.timeoutMs);
|
|
64
|
-
const forwardAbort = () => {
|
|
72
|
+
const forwardAbort = () => {
|
|
73
|
+
controller.abort();
|
|
74
|
+
};
|
|
65
75
|
options.signal?.addEventListener('abort', forwardAbort, { once: true });
|
|
66
76
|
let response;
|
|
67
77
|
try {
|
|
@@ -20,8 +20,12 @@ export const SALT_ENCODING = 'hex';
|
|
|
20
20
|
*/
|
|
21
21
|
export async function loadOrCreateSalt(options) {
|
|
22
22
|
const read = options.read ?? (async (path) => readFile(path, 'utf8'));
|
|
23
|
-
const write = options.write ??
|
|
24
|
-
|
|
23
|
+
const write = options.write ??
|
|
24
|
+
(async (path, data, mode) => writeFile(path, data, { mode }));
|
|
25
|
+
const makeDir = options.makeDir ??
|
|
26
|
+
(async (path) => {
|
|
27
|
+
await mkdir(path, { recursive: true });
|
|
28
|
+
});
|
|
25
29
|
const random = options.random ?? ((bytes) => randomBytes(bytes).toString(SALT_ENCODING));
|
|
26
30
|
try {
|
|
27
31
|
const existing = (await read(options.path)).trim();
|