@unlinearity/cli-wechat-bridge 1.0.4 → 1.0.6

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.
@@ -0,0 +1,175 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import net from "node:net";
4
+ import { DAEMON_ENDPOINT_FILE, ensureChannelDataDir, } from "../wechat/channel-config.js";
5
+ export const DAEMON_PROTOCOL_VERSION = 1;
6
+ function normalizeDaemonEndpoint(value) {
7
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
8
+ return null;
9
+ }
10
+ const record = value;
11
+ if (typeof record.protocolVersion !== "number" ||
12
+ typeof record.pid !== "number" ||
13
+ typeof record.port !== "number" ||
14
+ typeof record.token !== "string" ||
15
+ typeof record.cwd !== "string" ||
16
+ typeof record.startedAt !== "string") {
17
+ return null;
18
+ }
19
+ return {
20
+ protocolVersion: record.protocolVersion,
21
+ pid: record.pid,
22
+ port: record.port,
23
+ token: record.token,
24
+ cwd: record.cwd,
25
+ startedAt: record.startedAt,
26
+ };
27
+ }
28
+ export function buildDaemonToken() {
29
+ return crypto.randomBytes(18).toString("hex");
30
+ }
31
+ export function writeDaemonEndpoint(endpoint) {
32
+ ensureChannelDataDir();
33
+ fs.writeFileSync(DAEMON_ENDPOINT_FILE, JSON.stringify({
34
+ ...endpoint,
35
+ protocolVersion: DAEMON_PROTOCOL_VERSION,
36
+ }, null, 2), "utf8");
37
+ }
38
+ export function readDaemonEndpoint() {
39
+ try {
40
+ if (!fs.existsSync(DAEMON_ENDPOINT_FILE)) {
41
+ return null;
42
+ }
43
+ return normalizeDaemonEndpoint(JSON.parse(fs.readFileSync(DAEMON_ENDPOINT_FILE, "utf8")));
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
49
+ export function clearDaemonEndpoint(pid = process.pid) {
50
+ try {
51
+ const endpoint = readDaemonEndpoint();
52
+ if (!endpoint || endpoint.pid === pid) {
53
+ fs.rmSync(DAEMON_ENDPOINT_FILE, { force: true });
54
+ }
55
+ }
56
+ catch {
57
+ // Best effort cleanup.
58
+ }
59
+ }
60
+ export function isPidAlive(pid) {
61
+ if (!Number.isInteger(pid) || pid <= 0) {
62
+ return false;
63
+ }
64
+ try {
65
+ process.kill(pid, 0);
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ export function sendDaemonResponse(socket, id, response) {
73
+ if (socket.destroyed || socket.writableEnded) {
74
+ return false;
75
+ }
76
+ try {
77
+ return socket.write(`${JSON.stringify({ id, response })}\n`);
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ export function attachDaemonRequestListener(socket, onRequest) {
84
+ let buffer = "";
85
+ const onData = (chunk) => {
86
+ buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
87
+ while (true) {
88
+ const newlineIndex = buffer.indexOf("\n");
89
+ if (newlineIndex < 0) {
90
+ return;
91
+ }
92
+ const line = buffer.slice(0, newlineIndex).trim();
93
+ buffer = buffer.slice(newlineIndex + 1);
94
+ if (!line) {
95
+ continue;
96
+ }
97
+ try {
98
+ onRequest(JSON.parse(line));
99
+ }
100
+ catch {
101
+ // Ignore malformed daemon IPC frames.
102
+ }
103
+ }
104
+ };
105
+ socket.setEncoding("utf8");
106
+ socket.on("data", onData);
107
+ return () => {
108
+ socket.off("data", onData);
109
+ };
110
+ }
111
+ export async function sendDaemonRequest(endpoint, payload, options = {}) {
112
+ const timeoutMs = options.timeoutMs ?? 2_000;
113
+ const id = crypto.randomUUID();
114
+ return await new Promise((resolve) => {
115
+ const socket = net.connect({
116
+ host: "127.0.0.1",
117
+ port: endpoint.port,
118
+ });
119
+ let buffer = "";
120
+ let settled = false;
121
+ const timer = setTimeout(() => {
122
+ finish({ ok: false, error: "Timed out waiting for daemon response." });
123
+ }, timeoutMs);
124
+ const finish = (response) => {
125
+ if (settled) {
126
+ return;
127
+ }
128
+ settled = true;
129
+ clearTimeout(timer);
130
+ socket.destroy();
131
+ resolve(response);
132
+ };
133
+ socket.setEncoding("utf8");
134
+ socket.once("connect", () => {
135
+ socket.write(`${JSON.stringify({
136
+ id,
137
+ token: endpoint.token,
138
+ payload,
139
+ })}\n`);
140
+ });
141
+ socket.on("data", (chunk) => {
142
+ buffer += chunk;
143
+ while (true) {
144
+ const newlineIndex = buffer.indexOf("\n");
145
+ if (newlineIndex < 0) {
146
+ return;
147
+ }
148
+ const line = buffer.slice(0, newlineIndex).trim();
149
+ buffer = buffer.slice(newlineIndex + 1);
150
+ if (!line) {
151
+ continue;
152
+ }
153
+ try {
154
+ const frame = JSON.parse(line);
155
+ if (frame.id === id) {
156
+ finish(frame.response);
157
+ }
158
+ }
159
+ catch {
160
+ // Ignore malformed daemon IPC frames.
161
+ }
162
+ }
163
+ });
164
+ socket.once("error", () => {
165
+ finish({ ok: false, error: "Daemon endpoint is not reachable." });
166
+ });
167
+ });
168
+ }
169
+ export async function isDaemonEndpointAlive(endpoint, options = {}) {
170
+ if (!isPidAlive(endpoint.pid)) {
171
+ return false;
172
+ }
173
+ const response = await sendDaemonRequest(endpoint, { command: "status" }, options);
174
+ return response.ok;
175
+ }