mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-08-08 09:53:02 +08:00
fix(baoyu-post-to-wechat): make remote-api work under Bun & validate config strictly
The initial remote-api implementation (3b29f3c) relied on
`https.request({ agent: SocksProxyAgent })` to route token/upload/draft
calls through the SSH tunnel. Bun's `https.request` does not honor
Node's `http.Agent` contract, so the agent was silently bypassed and
requests still originated from the local IP — defeating the entire
IP-allowlist purpose. Two follow-on issues compounded it: tests read
the real `~/.baoyu-skills/.env` because Bun's `os.homedir()` ignores
test-time `process.env.HOME` mutations, and invalid config values were
silently coerced to defaults.
P1 — Bun-portable SOCKS routing:
- Drop `socks-proxy-agent` dependency. Add `socks` direct dep.
- New `wechat-socks-http.ts`: raw TCP via `SocksClient.createConnection`
+ `tls.connect({ socket, servername })` + hand-built HTTP/1.1 (status
line parser, case-insensitive headers, chunked & content-length body
framing). Works identically under Node and Bun because it avoids
`http.Agent` entirely.
- Rewrite `wechat-http.ts` as a fetch-based local client and expose
a `WechatClient = (url, init?) => Promise<WechatHttpResponse>`
functional abstraction.
- `wechat-api.ts`: replace `agent?: http.Agent` with
`client: WechatClient = wechatHttp` on the five HTTP-touching
functions; `withSshTunnel` now yields a `WechatClient`.
- New `wechat-socks-http.test.ts` stands up a real SOCKS5 server
stub + HTTP echo server and asserts `connectionCount === 1`,
proving bytes actually traverse the proxy under both runtimes.
P2 — `HOME` honored under Bun:
- `homeDir()` reads `process.env.HOME` / `USERPROFILE` first, falling
back to `os.homedir()`. `loadWechatExtendConfig` and `loadCredentials`
use it, restoring test isolation.
P3 — Strict config validation:
- Replace lenient `toOptional*` helpers with `parsePort` /
`parsePositiveInt` / `parseStrictHostKeyChecking` that throw with
the key name. `loadWechatExtendConfig` only catches file-read
errors so parse errors surface to the caller. Flip the corresponding
test cases.
Verification:
- `npm test`: 261/261 pass.
- `bun test` in `scripts/`: 39/39 pass.
Co-authored-by: Dame5211 <1079825614@qq.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { spawn, type ChildProcessByStdio } from "node:child_process";
|
||||
import net from "node:net";
|
||||
import { SocksProxyAgent } from "socks-proxy-agent";
|
||||
import type { Readable } from "node:stream";
|
||||
|
||||
import type { StrictHostKeyChecking } from "./wechat-extend-config.ts";
|
||||
import type { WechatClient } from "./wechat-http.ts";
|
||||
import { createSocksClient } from "./wechat-socks-http.ts";
|
||||
|
||||
export interface RemotePublishConfig {
|
||||
host: string;
|
||||
@@ -29,7 +30,7 @@ export interface NormalizedRemotePublishConfig {
|
||||
|
||||
export interface SshTunnel {
|
||||
port: number;
|
||||
agent: SocksProxyAgent;
|
||||
client: WechatClient;
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ export async function startSshTunnel(
|
||||
throw new Error(`${(err as Error).message}${exitSuffix}${suffix}`);
|
||||
}
|
||||
|
||||
const agent = new SocksProxyAgent(`socks5h://${SSH_LOOPBACK_HOST}:${port}`);
|
||||
const client = createSocksClient({ host: SSH_LOOPBACK_HOST, port });
|
||||
|
||||
const signalHandlers: Array<{ signal: NodeJS.Signals; handler: () => void }> = [];
|
||||
let closed = false;
|
||||
@@ -228,7 +229,7 @@ export async function startSshTunnel(
|
||||
signalHandlers.push({ signal, handler });
|
||||
}
|
||||
|
||||
return { port, agent, close };
|
||||
return { port, client, close };
|
||||
}
|
||||
|
||||
async function killChild(child: ChildProcessByStdio<null, Readable, Readable>, killTimeoutMs: number): Promise<void> {
|
||||
@@ -261,12 +262,12 @@ async function killChild(child: ChildProcessByStdio<null, Readable, Readable>, k
|
||||
|
||||
export async function withSshTunnel<T>(
|
||||
config: NormalizedRemotePublishConfig,
|
||||
fn: (agent: SocksProxyAgent) => Promise<T>,
|
||||
fn: (client: WechatClient) => Promise<T>,
|
||||
options?: StartSshTunnelOptions,
|
||||
): Promise<T> {
|
||||
const tunnel = await startSshTunnel(config, options);
|
||||
try {
|
||||
return await fn(tunnel.agent);
|
||||
return await fn(tunnel.client);
|
||||
} finally {
|
||||
await tunnel.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user