@agentunion/fastaun 0.3.2 → 0.3.4
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/CHANGELOG.md +43 -0
- package/_packed_docs/CHANGELOG.md +43 -0
- package/_packed_docs/INDEX.md +81 -0
- package/_packed_docs/KITE_DOCS_GUIDE.md +55 -0
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +328 -0
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -0
- package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -0
- package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +5 -5
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +454 -396
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1410 -1244
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +19 -1
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +20 -5
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
- package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -0
- package/_packed_docs/sdk/INDEX.md +9 -4
- package/_packed_docs/sdk/README.md +3 -3
- package/dist/auth.d.ts +44 -8
- package/dist/auth.js +398 -119
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +123 -19
- package/dist/client.js +2650 -673
- package/dist/client.js.map +1 -1
- package/dist/discovery.d.ts +4 -0
- package/dist/discovery.js +28 -13
- package/dist/discovery.js.map +1 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +9 -0
- package/dist/events.js +42 -12
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +4 -0
- package/dist/keystore/aid-db.js +94 -0
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/file.d.ts +23 -1
- package/dist/keystore/file.js +109 -1
- package/dist/keystore/file.js.map +1 -1
- package/dist/keystore/index.d.ts +20 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +7 -4
- package/dist/logger.js.map +1 -1
- package/dist/namespaces/auth.d.ts +34 -4
- package/dist/namespaces/auth.js +194 -51
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/net.d.ts +43 -0
- package/dist/net.js +192 -0
- package/dist/net.js.map +1 -0
- package/dist/secret-store/file-store.d.ts +21 -2
- package/dist/secret-store/file-store.js +166 -11
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +32 -3
- package/dist/seq-tracker.js +60 -3
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +2 -0
- package/dist/tools/cross-sdk-agent.js +695 -0
- package/dist/tools/cross-sdk-agent.js.map +1 -0
- package/dist/transport.d.ts +10 -1
- package/dist/transport.js +196 -32
- package/dist/transport.js.map +1 -1
- package/dist/v2/crypto/canonical.d.ts +1 -1
- package/dist/v2/crypto/canonical.js +42 -17
- package/dist/v2/crypto/canonical.js.map +1 -1
- package/dist/v2/e2ee/decrypt.js +57 -3
- package/dist/v2/e2ee/decrypt.js.map +1 -1
- package/dist/v2/e2ee/encrypt-group.js +16 -7
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +42 -9
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/metadata-auth.d.ts +1 -0
- package/dist/v2/e2ee/metadata-auth.js +37 -1
- package/dist/v2/e2ee/metadata-auth.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +2 -2
- package/dist/v2/session/keystore.d.ts +10 -3
- package/dist/v2/session/keystore.js +158 -30
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/v2/session/session.d.ts +7 -3
- package/dist/v2/session/session.js +64 -12
- package/dist/v2/session/session.js.map +1 -1
- package/package.json +46 -46
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import { URL } from 'node:url';
|
|
6
|
+
import { AUNClient } from '../client.js';
|
|
7
|
+
import { certificateSha256Fingerprint } from '../crypto.js';
|
|
8
|
+
function envBool(name, fallback = false) {
|
|
9
|
+
const raw = process.env[name];
|
|
10
|
+
if (raw == null)
|
|
11
|
+
return fallback;
|
|
12
|
+
return ['1', 'true', 'yes', 'on'].includes(raw.trim().toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
function jsonSafe(value) {
|
|
15
|
+
try {
|
|
16
|
+
JSON.stringify(value);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
return value.map((item) => jsonSafe(item));
|
|
22
|
+
if (value && typeof value === 'object') {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [key, item] of Object.entries(value))
|
|
25
|
+
out[key] = jsonSafe(item);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
return String(value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function stableStringify(value) {
|
|
32
|
+
if (value === null || value === undefined)
|
|
33
|
+
return 'null';
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
return JSON.stringify(value);
|
|
36
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
37
|
+
return JSON.stringify(value);
|
|
38
|
+
if (Array.isArray(value))
|
|
39
|
+
return `[${value.map((item) => stableStringify(item)).join(',')}]`;
|
|
40
|
+
if (typeof value === 'object') {
|
|
41
|
+
const obj = value;
|
|
42
|
+
return `{${Object.keys(obj).sort().map((key) => `${stableStringify(key)}:${stableStringify(obj[key])}`).join(',')}}`;
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(String(value));
|
|
45
|
+
}
|
|
46
|
+
function sha256Json(value) {
|
|
47
|
+
return crypto.createHash('sha256').update(stableStringify(jsonSafe(value)), 'utf-8').digest('hex');
|
|
48
|
+
}
|
|
49
|
+
function textOf(value) {
|
|
50
|
+
return String(value ?? '');
|
|
51
|
+
}
|
|
52
|
+
function jsonObjectOf(value) {
|
|
53
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
54
|
+
}
|
|
55
|
+
function envelopeMetadata(data) {
|
|
56
|
+
const e2ee = jsonObjectOf(data.e2ee);
|
|
57
|
+
const protectedHeaders = jsonObjectOf(data.protected_headers) ?? jsonObjectOf(e2ee?.protected_headers);
|
|
58
|
+
const payloadType = textOf(data.payload_type ?? protectedHeaders?.payload_type ?? e2ee?.payload_type).trim();
|
|
59
|
+
const out = {};
|
|
60
|
+
if (payloadType)
|
|
61
|
+
out.payload_type = payloadType;
|
|
62
|
+
if (protectedHeaders)
|
|
63
|
+
out.protected_headers = jsonSafe(protectedHeaders);
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
async function readJson(req) {
|
|
67
|
+
const chunks = [];
|
|
68
|
+
for await (const chunk of req) {
|
|
69
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
70
|
+
}
|
|
71
|
+
if (!chunks.length)
|
|
72
|
+
return {};
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString('utf-8'));
|
|
75
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function sendJson(res, status, data) {
|
|
82
|
+
const body = JSON.stringify(jsonSafe(data));
|
|
83
|
+
res.writeHead(status, {
|
|
84
|
+
'content-type': 'application/json; charset=utf-8',
|
|
85
|
+
'content-length': Buffer.byteLength(body),
|
|
86
|
+
});
|
|
87
|
+
res.end(body);
|
|
88
|
+
}
|
|
89
|
+
class CrossSdkTsAgent {
|
|
90
|
+
language = 'ts';
|
|
91
|
+
sdkVersion = 'unknown';
|
|
92
|
+
aid = textOf(process.env.AUN_TEST_AID || 'cross-ts.agentid.pub').trim();
|
|
93
|
+
issuer = textOf(process.env.AUN_TEST_ISSUER || 'agentid.pub').trim() || 'agentid.pub';
|
|
94
|
+
gatewayAid = textOf(process.env.AUN_GATEWAY_AID || `gateway.${this.issuer}`).trim();
|
|
95
|
+
gatewayUrl = textOf(process.env.AUN_GATEWAY_URL || '').trim();
|
|
96
|
+
slotId = textOf(process.env.AUN_TEST_SLOT_ID || `cross-sdk-ts-${crypto.randomUUID().slice(0, 8)}`).trim();
|
|
97
|
+
aunPath = textOf(process.env.AUN_TEST_AUN_PATH || process.env.AUN_DATA_ROOT || '/data/aun').trim();
|
|
98
|
+
debug = envBool('AUN_TEST_DEBUG', false);
|
|
99
|
+
client;
|
|
100
|
+
ready = false;
|
|
101
|
+
startupError = '';
|
|
102
|
+
inbox = [];
|
|
103
|
+
groupInbox = [];
|
|
104
|
+
traces = new Map();
|
|
105
|
+
sendResults = new Map();
|
|
106
|
+
constructor() {
|
|
107
|
+
this.client = new AUNClient({
|
|
108
|
+
aun_path: this.aunPath,
|
|
109
|
+
requireForwardSecrecy: false,
|
|
110
|
+
debug: this.debug,
|
|
111
|
+
}, this.debug);
|
|
112
|
+
const internal = this.client;
|
|
113
|
+
if (internal._configModel)
|
|
114
|
+
internal._configModel.requireForwardSecrecy = false;
|
|
115
|
+
internal._testSlotId = this.slotId;
|
|
116
|
+
if (this.gatewayUrl)
|
|
117
|
+
internal._gatewayUrl = this.gatewayUrl;
|
|
118
|
+
}
|
|
119
|
+
async start() {
|
|
120
|
+
this.client.on('message.received', (msg) => {
|
|
121
|
+
void this.storeInboxItem(this.normalizeMessage(msg, true));
|
|
122
|
+
});
|
|
123
|
+
this.client.on('message.undecryptable', (msg) => {
|
|
124
|
+
void this.storeInboxItem(this.normalizeMessage(msg, false, 'undecryptable'));
|
|
125
|
+
});
|
|
126
|
+
this.client.on('group.message_created', (msg) => {
|
|
127
|
+
void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, true));
|
|
128
|
+
});
|
|
129
|
+
this.client.on('group.message_undecryptable', (msg) => {
|
|
130
|
+
void this.storeGroupInboxItem(this.normalizeGroupMessage(msg, false, 'undecryptable'));
|
|
131
|
+
});
|
|
132
|
+
await this.ensureConnected();
|
|
133
|
+
this.ready = true;
|
|
134
|
+
}
|
|
135
|
+
async close() {
|
|
136
|
+
try {
|
|
137
|
+
await this.client.close();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// 退出清理失败不影响容器关闭。
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async ensureConnected() {
|
|
144
|
+
const internal = this.client;
|
|
145
|
+
if (this.gatewayUrl) {
|
|
146
|
+
internal._gatewayUrl = this.gatewayUrl;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const authAny = this.client.auth;
|
|
150
|
+
internal._gatewayUrl = await authAny._resolveGateway(this.gatewayAid);
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
await this.client.auth.registerAid({ aid: this.aid });
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
const localIdentity = internal._auth?.loadIdentityOrNone?.(this.aid);
|
|
157
|
+
if (!localIdentity) {
|
|
158
|
+
throw new Error(`registerAid failed and no local identity exists: ${err instanceof Error ? err.message : String(err)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const auth = await this.client.auth.authenticate({ aid: this.aid });
|
|
162
|
+
const params = {
|
|
163
|
+
...auth,
|
|
164
|
+
slot_id: this.slotId,
|
|
165
|
+
auto_reconnect: envBool('AUN_TEST_AUTO_RECONNECT', false),
|
|
166
|
+
background_sync: true,
|
|
167
|
+
};
|
|
168
|
+
await this.client.connect(params);
|
|
169
|
+
}
|
|
170
|
+
identity() {
|
|
171
|
+
const internal = this.client;
|
|
172
|
+
const identity = internal._identity ?? internal._auth?.loadIdentityOrNone?.(this.aid) ?? {};
|
|
173
|
+
const cert = textOf(identity.cert ?? identity.cert_pem ?? '');
|
|
174
|
+
return {
|
|
175
|
+
aid: this.aid,
|
|
176
|
+
device_id: textOf(internal._deviceId ?? ''),
|
|
177
|
+
slot_id: textOf(internal._slotId ?? this.slotId),
|
|
178
|
+
issuer: this.issuer,
|
|
179
|
+
public_key_fingerprint: cert ? certificateSha256Fingerprint(cert) : '',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
recordTrace(traceId, item) {
|
|
183
|
+
if (!traceId)
|
|
184
|
+
return;
|
|
185
|
+
const current = this.traces.get(traceId) ?? [];
|
|
186
|
+
current.push({
|
|
187
|
+
ts: Date.now(),
|
|
188
|
+
language: this.language,
|
|
189
|
+
aid: this.aid,
|
|
190
|
+
...item,
|
|
191
|
+
});
|
|
192
|
+
this.traces.set(traceId, current);
|
|
193
|
+
}
|
|
194
|
+
async storeInboxItem(item) {
|
|
195
|
+
this.inbox.push(item);
|
|
196
|
+
if (this.inbox.length > 1000)
|
|
197
|
+
this.inbox = this.inbox.slice(-1000);
|
|
198
|
+
this.recordTrace(textOf(item.trace_id), { stage: 'receive', message: item });
|
|
199
|
+
}
|
|
200
|
+
normalizeMessage(msg, decrypted, errorCode = '') {
|
|
201
|
+
const data = msg && typeof msg === 'object' && !Array.isArray(msg) ? msg : { raw: textOf(msg) };
|
|
202
|
+
const payload = data.payload && typeof data.payload === 'object' ? data.payload : {};
|
|
203
|
+
const traceId = textOf(payload.trace_id ?? data.trace_id ?? '');
|
|
204
|
+
const text = textOf(payload.text ?? data.text ?? '');
|
|
205
|
+
const metadata = envelopeMetadata(data);
|
|
206
|
+
return {
|
|
207
|
+
trace_id: traceId,
|
|
208
|
+
message_id: textOf(data.message_id ?? data.id ?? ''),
|
|
209
|
+
from: textOf(data.from ?? data.from_aid ?? ''),
|
|
210
|
+
to: textOf(data.to ?? data.to_aid ?? this.aid),
|
|
211
|
+
text,
|
|
212
|
+
decrypted,
|
|
213
|
+
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
214
|
+
seq: Number(data.seq ?? data.message_seq ?? 0) || 0,
|
|
215
|
+
ack_seq: Number(data.ack_seq ?? 0) || 0,
|
|
216
|
+
error_code: errorCode,
|
|
217
|
+
raw_sha256: sha256Json(data),
|
|
218
|
+
...metadata,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
normalizeGroupMessage(msg, decrypted, errorCode = '') {
|
|
222
|
+
const data = msg && typeof msg === 'object' && !Array.isArray(msg) ? msg : { raw: textOf(msg) };
|
|
223
|
+
const payload = data.payload && typeof data.payload === 'object' && !Array.isArray(data.payload) ? data.payload : {};
|
|
224
|
+
const traceId = textOf(payload.trace_id ?? data.trace_id ?? '');
|
|
225
|
+
const text = textOf(payload.text ?? data.text ?? '');
|
|
226
|
+
const metadata = envelopeMetadata(data);
|
|
227
|
+
return {
|
|
228
|
+
trace_id: traceId,
|
|
229
|
+
group_id: textOf(data.group_id ?? ''),
|
|
230
|
+
message_id: textOf(data.message_id ?? data.id ?? ''),
|
|
231
|
+
from: textOf(data.from ?? data.from_aid ?? data.sender_aid ?? ''),
|
|
232
|
+
text,
|
|
233
|
+
decrypted,
|
|
234
|
+
encrypted: Boolean(data.e2ee ?? data.encrypted),
|
|
235
|
+
seq: Number(data.seq ?? data.message_seq ?? data.msg_seq ?? 0) || 0,
|
|
236
|
+
error_code: errorCode,
|
|
237
|
+
raw_sha256: sha256Json(data),
|
|
238
|
+
...metadata,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async storeGroupInboxItem(item) {
|
|
242
|
+
this.groupInbox.push(item);
|
|
243
|
+
if (this.groupInbox.length > 1000)
|
|
244
|
+
this.groupInbox = this.groupInbox.slice(-1000);
|
|
245
|
+
this.recordTrace(textOf(item.trace_id), { stage: 'group_receive', message: item });
|
|
246
|
+
}
|
|
247
|
+
async handle(req, res) {
|
|
248
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
249
|
+
try {
|
|
250
|
+
if (req.method === 'GET' && url.pathname === '/health') {
|
|
251
|
+
const state = textOf(this.client._state ?? '');
|
|
252
|
+
sendJson(res, this.startupError ? 503 : 200, {
|
|
253
|
+
ok: !this.startupError,
|
|
254
|
+
agent_ready: this.ready && state === 'connected',
|
|
255
|
+
state,
|
|
256
|
+
aid: this.aid,
|
|
257
|
+
language: this.language,
|
|
258
|
+
sdk_version: this.sdkVersion,
|
|
259
|
+
gateway_url: textOf(this.client._gatewayUrl ?? ''),
|
|
260
|
+
startup_error: this.startupError,
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (req.method === 'POST' && url.pathname === '/reset') {
|
|
265
|
+
const body = await readJson(req);
|
|
266
|
+
const traceId = textOf(body.trace_id ?? '');
|
|
267
|
+
if (traceId) {
|
|
268
|
+
this.inbox = this.inbox.filter((item) => item.trace_id !== traceId);
|
|
269
|
+
this.groupInbox = this.groupInbox.filter((item) => item.trace_id !== traceId);
|
|
270
|
+
this.traces.delete(traceId);
|
|
271
|
+
this.sendResults.delete(traceId);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
this.inbox = [];
|
|
275
|
+
this.groupInbox = [];
|
|
276
|
+
this.traces.clear();
|
|
277
|
+
this.sendResults.clear();
|
|
278
|
+
}
|
|
279
|
+
sendJson(res, 200, { ok: true });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (req.method === 'GET' && url.pathname === '/identity') {
|
|
283
|
+
sendJson(res, 200, this.identity());
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (req.method === 'POST' && url.pathname === '/send') {
|
|
287
|
+
await this.send(req, res);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (req.method === 'POST' && url.pathname === '/ack') {
|
|
291
|
+
await this.ack(req, res);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (req.method === 'POST' && url.pathname === '/pull') {
|
|
295
|
+
await this.pull(req, res);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (req.method === 'POST' && url.pathname === '/group/create') {
|
|
299
|
+
await this.groupCreate(req, res);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (req.method === 'GET' && url.pathname === '/group/ready') {
|
|
303
|
+
await this.groupReady(url, res);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (req.method === 'POST' && url.pathname === '/group/send') {
|
|
307
|
+
await this.groupSend(req, res);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (req.method === 'POST' && url.pathname === '/group/pull') {
|
|
311
|
+
await this.groupPull(req, res);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (req.method === 'POST' && url.pathname === '/group/ack') {
|
|
315
|
+
await this.groupAck(req, res);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (req.method === 'GET' && url.pathname === '/inbox') {
|
|
319
|
+
const traceId = textOf(url.searchParams.get('trace_id') ?? '');
|
|
320
|
+
const fromAid = textOf(url.searchParams.get('from') ?? '');
|
|
321
|
+
const limit = Number(url.searchParams.get('limit') ?? 20) || 20;
|
|
322
|
+
let items = [...this.inbox];
|
|
323
|
+
if (traceId)
|
|
324
|
+
items = items.filter((item) => item.trace_id === traceId);
|
|
325
|
+
if (fromAid)
|
|
326
|
+
items = items.filter((item) => item.from === fromAid);
|
|
327
|
+
items = items.slice(-limit);
|
|
328
|
+
sendJson(res, 200, { received: items.length > 0, items });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (req.method === 'GET' && url.pathname === '/group/inbox') {
|
|
332
|
+
const traceId = textOf(url.searchParams.get('trace_id') ?? '');
|
|
333
|
+
const groupId = textOf(url.searchParams.get('group_id') ?? '');
|
|
334
|
+
const fromAid = textOf(url.searchParams.get('from') ?? '');
|
|
335
|
+
const limit = Number(url.searchParams.get('limit') ?? 20) || 20;
|
|
336
|
+
let items = [...this.groupInbox];
|
|
337
|
+
if (traceId)
|
|
338
|
+
items = items.filter((item) => item.trace_id === traceId);
|
|
339
|
+
if (groupId)
|
|
340
|
+
items = items.filter((item) => item.group_id === groupId);
|
|
341
|
+
if (fromAid)
|
|
342
|
+
items = items.filter((item) => item.from === fromAid);
|
|
343
|
+
items = items.slice(-limit);
|
|
344
|
+
sendJson(res, 200, { received: items.length > 0, items });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (req.method === 'GET' && url.pathname.startsWith('/traces/')) {
|
|
348
|
+
const traceId = decodeURIComponent(url.pathname.slice('/traces/'.length));
|
|
349
|
+
sendJson(res, 200, { trace_id: traceId, items: this.traces.get(traceId) ?? [] });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (req.method === 'GET' && url.pathname === '/logs') {
|
|
353
|
+
const logDir = textOf(process.env.AUN_LOG_DIR || '/root/.aun/logs');
|
|
354
|
+
let files = [];
|
|
355
|
+
try {
|
|
356
|
+
files = fs.existsSync(logDir)
|
|
357
|
+
? fs.readdirSync(logDir, { recursive: true }).map((p) => `${logDir}/${String(p)}`).slice(-20)
|
|
358
|
+
: [];
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
files = [];
|
|
362
|
+
}
|
|
363
|
+
sendJson(res, 200, { log_files: files, tail: [] });
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
sendJson(res, 404, { ok: false, error_code: 'not_found', error_message: url.pathname });
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
sendJson(res, 500, {
|
|
370
|
+
ok: false,
|
|
371
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
372
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async send(req, res) {
|
|
377
|
+
const body = await readJson(req);
|
|
378
|
+
const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
|
|
379
|
+
const messageId = textOf(body.message_id || `${traceId}-${crypto.randomUUID().slice(0, 8)}`);
|
|
380
|
+
const target = textOf(body.to).trim();
|
|
381
|
+
const text = textOf(body.text);
|
|
382
|
+
const e2ee = body.e2ee !== false;
|
|
383
|
+
if (!target) {
|
|
384
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'to is required' });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const payload = {
|
|
388
|
+
type: 'text',
|
|
389
|
+
text,
|
|
390
|
+
trace_id: traceId,
|
|
391
|
+
case_id: textOf(body.case_id || traceId),
|
|
392
|
+
};
|
|
393
|
+
const params = {
|
|
394
|
+
to: target,
|
|
395
|
+
payload,
|
|
396
|
+
encrypt: e2ee,
|
|
397
|
+
message_id: messageId,
|
|
398
|
+
};
|
|
399
|
+
try {
|
|
400
|
+
const result = await this.client.call('message.send', params);
|
|
401
|
+
const resultObj = result && typeof result === 'object' ? result : {};
|
|
402
|
+
const response = {
|
|
403
|
+
ok: true,
|
|
404
|
+
trace_id: traceId,
|
|
405
|
+
message_id: messageId,
|
|
406
|
+
seq: Number(resultObj.seq ?? resultObj.message_seq ?? 0) || 0,
|
|
407
|
+
encrypted: e2ee,
|
|
408
|
+
result: jsonSafe(result),
|
|
409
|
+
};
|
|
410
|
+
this.sendResults.set(traceId, response);
|
|
411
|
+
this.recordTrace(traceId, { stage: 'send', target, message_id: messageId, result: response });
|
|
412
|
+
sendJson(res, 200, response);
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
const error = {
|
|
416
|
+
ok: false,
|
|
417
|
+
trace_id: traceId,
|
|
418
|
+
message_id: messageId,
|
|
419
|
+
encrypted: e2ee,
|
|
420
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
421
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
422
|
+
};
|
|
423
|
+
this.sendResults.set(traceId, error);
|
|
424
|
+
this.recordTrace(traceId, { stage: 'send_error', target, message_id: messageId, error });
|
|
425
|
+
sendJson(res, 500, error);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async ack(req, res) {
|
|
429
|
+
const body = await readJson(req);
|
|
430
|
+
const seq = Number(body.seq ?? body.up_to_seq ?? 0) || 0;
|
|
431
|
+
const params = {};
|
|
432
|
+
if (seq > 0)
|
|
433
|
+
params.seq = seq;
|
|
434
|
+
try {
|
|
435
|
+
const result = await this.client.call('message.ack', params);
|
|
436
|
+
sendJson(res, 200, { ok: true, seq, result: jsonSafe(result) });
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
sendJson(res, 500, {
|
|
440
|
+
ok: false,
|
|
441
|
+
seq,
|
|
442
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
443
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async pull(req, res) {
|
|
448
|
+
const body = await readJson(req);
|
|
449
|
+
const afterSeq = Number(body.after_seq ?? 0) || 0;
|
|
450
|
+
const limit = Number(body.limit ?? 50) || 50;
|
|
451
|
+
try {
|
|
452
|
+
const result = await this.client.call('message.pull', { after_seq: afterSeq, limit });
|
|
453
|
+
const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
|
|
454
|
+
const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
|
|
455
|
+
for (const msg of messages) {
|
|
456
|
+
await this.storeInboxItem(this.normalizeMessage(msg, true));
|
|
457
|
+
}
|
|
458
|
+
sendJson(res, 200, { ok: true, result: jsonSafe(result) });
|
|
459
|
+
}
|
|
460
|
+
catch (err) {
|
|
461
|
+
sendJson(res, 500, {
|
|
462
|
+
ok: false,
|
|
463
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
464
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async groupCreate(req, res) {
|
|
469
|
+
const body = await readJson(req);
|
|
470
|
+
const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
|
|
471
|
+
const name = textOf(body.name || `cross-sdk-${traceId.slice(0, 8)}`);
|
|
472
|
+
const members = Array.isArray(body.members) ? body.members.map((item) => textOf(item).trim()).filter(Boolean) : [];
|
|
473
|
+
const params = {
|
|
474
|
+
name,
|
|
475
|
+
visibility: textOf(body.visibility || 'private'),
|
|
476
|
+
};
|
|
477
|
+
const joinMode = textOf(body.join_mode || '').trim();
|
|
478
|
+
if (joinMode)
|
|
479
|
+
params.join_mode = joinMode;
|
|
480
|
+
try {
|
|
481
|
+
const createResult = await this.client.call('group.create', params);
|
|
482
|
+
const groupId = this.extractGroupId(createResult);
|
|
483
|
+
if (!groupId)
|
|
484
|
+
throw new Error(`group.create did not return group_id: ${JSON.stringify(jsonSafe(createResult))}`);
|
|
485
|
+
const addResults = [];
|
|
486
|
+
for (const aid of members) {
|
|
487
|
+
if (!aid || aid === this.aid)
|
|
488
|
+
continue;
|
|
489
|
+
const addResult = await this.client.call('group.add_member', {
|
|
490
|
+
group_id: groupId,
|
|
491
|
+
aid,
|
|
492
|
+
role: 'member',
|
|
493
|
+
});
|
|
494
|
+
addResults.push(jsonSafe(addResult));
|
|
495
|
+
}
|
|
496
|
+
const response = {
|
|
497
|
+
ok: true,
|
|
498
|
+
trace_id: traceId,
|
|
499
|
+
group_id: groupId,
|
|
500
|
+
create_result: jsonSafe(createResult),
|
|
501
|
+
add_results: addResults,
|
|
502
|
+
};
|
|
503
|
+
this.recordTrace(traceId, { stage: 'group_create', group_id: groupId, result: response });
|
|
504
|
+
sendJson(res, 200, response);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
const error = {
|
|
508
|
+
ok: false,
|
|
509
|
+
trace_id: traceId,
|
|
510
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
511
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
512
|
+
};
|
|
513
|
+
this.recordTrace(traceId, { stage: 'group_create_error', error });
|
|
514
|
+
sendJson(res, 500, error);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async groupReady(url, res) {
|
|
518
|
+
const groupId = textOf(url.searchParams.get('group_id') ?? '').trim();
|
|
519
|
+
const expected = textOf(url.searchParams.get('members') ?? this.aid)
|
|
520
|
+
.split(',')
|
|
521
|
+
.map((item) => item.trim())
|
|
522
|
+
.filter(Boolean);
|
|
523
|
+
const requireDevices = envBool('CROSS_SDK_GROUP_READY_REQUIRE_DEVICES', true);
|
|
524
|
+
if (!groupId) {
|
|
525
|
+
sendJson(res, 400, { ok: false, ready: false, error_code: 'bad_request', error_message: 'group_id is required' });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const bootstrap = await this.client.call('group.v2.bootstrap', { group_id: groupId });
|
|
530
|
+
const committedRaw = Array.isArray(bootstrap.committed_member_aids)
|
|
531
|
+
? bootstrap.committed_member_aids
|
|
532
|
+
: (Array.isArray(bootstrap.member_aids) ? bootstrap.member_aids : []);
|
|
533
|
+
const committed = new Set(committedRaw.map((item) => textOf(item)));
|
|
534
|
+
const devices = Array.isArray(bootstrap.devices) ? bootstrap.devices : [];
|
|
535
|
+
const deviceAids = new Set(devices
|
|
536
|
+
.filter((item) => item && typeof item === 'object' && !Array.isArray(item))
|
|
537
|
+
.map((item) => textOf(item.aid)));
|
|
538
|
+
const membershipOk = expected.every((aid) => committed.has(aid));
|
|
539
|
+
const devicesOk = !requireDevices || expected.every((aid) => deviceAids.has(aid));
|
|
540
|
+
sendJson(res, 200, {
|
|
541
|
+
ok: true,
|
|
542
|
+
ready: membershipOk && devicesOk,
|
|
543
|
+
group_id: groupId,
|
|
544
|
+
expected,
|
|
545
|
+
committed_member_aids: [...committed].sort(),
|
|
546
|
+
device_aids: [...deviceAids].sort(),
|
|
547
|
+
pending_adds: Array.isArray(bootstrap.pending_adds) ? bootstrap.pending_adds : [],
|
|
548
|
+
bootstrap: jsonSafe(bootstrap),
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
sendJson(res, 500, {
|
|
553
|
+
ok: false,
|
|
554
|
+
ready: false,
|
|
555
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
556
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async groupSend(req, res) {
|
|
561
|
+
const body = await readJson(req);
|
|
562
|
+
const traceId = textOf(body.trace_id || crypto.randomUUID().replace(/-/g, ''));
|
|
563
|
+
const messageId = textOf(body.message_id || `${traceId}-${crypto.randomUUID().slice(0, 8)}`);
|
|
564
|
+
const groupId = textOf(body.group_id).trim();
|
|
565
|
+
const text = textOf(body.text);
|
|
566
|
+
const e2ee = body.e2ee !== false;
|
|
567
|
+
if (!groupId) {
|
|
568
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'group_id is required' });
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const payload = {
|
|
572
|
+
type: 'text',
|
|
573
|
+
text,
|
|
574
|
+
trace_id: traceId,
|
|
575
|
+
case_id: textOf(body.case_id || traceId),
|
|
576
|
+
};
|
|
577
|
+
try {
|
|
578
|
+
const result = await this.client.call('group.send', {
|
|
579
|
+
group_id: groupId,
|
|
580
|
+
payload,
|
|
581
|
+
encrypt: e2ee,
|
|
582
|
+
message_id: messageId,
|
|
583
|
+
});
|
|
584
|
+
const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
|
|
585
|
+
const response = {
|
|
586
|
+
ok: true,
|
|
587
|
+
trace_id: traceId,
|
|
588
|
+
group_id: groupId,
|
|
589
|
+
message_id: messageId,
|
|
590
|
+
seq: Number(resultObj.seq ?? resultObj.message_seq ?? 0) || 0,
|
|
591
|
+
encrypted: e2ee,
|
|
592
|
+
result: jsonSafe(result),
|
|
593
|
+
};
|
|
594
|
+
this.recordTrace(traceId, { stage: 'group_send', group_id: groupId, message_id: messageId, result: response });
|
|
595
|
+
sendJson(res, 200, response);
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
const error = {
|
|
599
|
+
ok: false,
|
|
600
|
+
trace_id: traceId,
|
|
601
|
+
group_id: groupId,
|
|
602
|
+
message_id: messageId,
|
|
603
|
+
encrypted: e2ee,
|
|
604
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
605
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
606
|
+
};
|
|
607
|
+
this.recordTrace(traceId, { stage: 'group_send_error', group_id: groupId, message_id: messageId, error });
|
|
608
|
+
sendJson(res, 500, error);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async groupPull(req, res) {
|
|
612
|
+
const body = await readJson(req);
|
|
613
|
+
const groupId = textOf(body.group_id).trim();
|
|
614
|
+
const afterSeq = Number(body.after_seq ?? 0) || 0;
|
|
615
|
+
const limit = Number(body.limit ?? 50) || 50;
|
|
616
|
+
if (!groupId) {
|
|
617
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'group_id is required' });
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const result = await this.client.call('group.pull', { group_id: groupId, after_seq: afterSeq, limit });
|
|
621
|
+
const resultObj = result && typeof result === 'object' && !Array.isArray(result) ? result : {};
|
|
622
|
+
const messages = Array.isArray(resultObj.messages) ? resultObj.messages : [];
|
|
623
|
+
for (const msg of messages) {
|
|
624
|
+
await this.storeGroupInboxItem(this.normalizeGroupMessage(msg, true));
|
|
625
|
+
}
|
|
626
|
+
sendJson(res, 200, { ok: true, group_id: groupId, result: jsonSafe(result) });
|
|
627
|
+
}
|
|
628
|
+
async groupAck(req, res) {
|
|
629
|
+
const body = await readJson(req);
|
|
630
|
+
const groupId = textOf(body.group_id).trim();
|
|
631
|
+
const seq = Number(body.seq ?? body.msg_seq ?? body.up_to_seq ?? 0) || 0;
|
|
632
|
+
if (!groupId) {
|
|
633
|
+
sendJson(res, 400, { ok: false, error_code: 'bad_request', error_message: 'group_id is required' });
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const params = { group_id: groupId };
|
|
637
|
+
if (seq > 0) {
|
|
638
|
+
params.msg_seq = seq;
|
|
639
|
+
params.up_to_seq = seq;
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
const result = await this.client.call('group.ack_messages', params);
|
|
643
|
+
sendJson(res, 200, { ok: true, group_id: groupId, seq, result: jsonSafe(result) });
|
|
644
|
+
}
|
|
645
|
+
catch (err) {
|
|
646
|
+
sendJson(res, 500, {
|
|
647
|
+
ok: false,
|
|
648
|
+
group_id: groupId,
|
|
649
|
+
seq,
|
|
650
|
+
error_code: err instanceof Error ? err.name : 'Error',
|
|
651
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
extractGroupId(result) {
|
|
656
|
+
if (!result || typeof result !== 'object' || Array.isArray(result))
|
|
657
|
+
return '';
|
|
658
|
+
const obj = result;
|
|
659
|
+
if (obj.group_id)
|
|
660
|
+
return textOf(obj.group_id);
|
|
661
|
+
const group = obj.group;
|
|
662
|
+
if (group && typeof group === 'object' && !Array.isArray(group) && group.group_id) {
|
|
663
|
+
return textOf(group.group_id);
|
|
664
|
+
}
|
|
665
|
+
const member = obj.member;
|
|
666
|
+
if (member && typeof member === 'object' && !Array.isArray(member) && member.group_id) {
|
|
667
|
+
return textOf(member.group_id);
|
|
668
|
+
}
|
|
669
|
+
return '';
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
async function main() {
|
|
673
|
+
const agent = new CrossSdkTsAgent();
|
|
674
|
+
void agent.start().catch((err) => {
|
|
675
|
+
agent.startupError = `${err instanceof Error ? err.name : 'Error'}: ${err instanceof Error ? err.message : String(err)}`;
|
|
676
|
+
console.error(agent.startupError);
|
|
677
|
+
});
|
|
678
|
+
const host = textOf(process.env.AUN_CONTROL_HOST || '0.0.0.0');
|
|
679
|
+
const port = Number(process.env.AUN_CONTROL_PORT || 9001) || 9001;
|
|
680
|
+
const server = http.createServer((req, res) => {
|
|
681
|
+
void agent.handle(req, res);
|
|
682
|
+
});
|
|
683
|
+
server.listen(port, host, () => {
|
|
684
|
+
console.log(`cross-sdk ts agent listening on ${host}:${port}`);
|
|
685
|
+
});
|
|
686
|
+
const shutdown = async () => {
|
|
687
|
+
server.close();
|
|
688
|
+
await agent.close();
|
|
689
|
+
process.exit(0);
|
|
690
|
+
};
|
|
691
|
+
process.on('SIGINT', () => { void shutdown(); });
|
|
692
|
+
process.on('SIGTERM', () => { void shutdown(); });
|
|
693
|
+
}
|
|
694
|
+
void main();
|
|
695
|
+
//# sourceMappingURL=cross-sdk-agent.js.map
|