@agentvault/secure-channel 0.6.15 → 0.6.16
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/dist/__tests__/install-plugin.test.d.ts +2 -0
- package/dist/__tests__/install-plugin.test.d.ts.map +1 -0
- package/dist/channel.d.ts +11 -0
- package/dist/channel.d.ts.map +1 -1
- package/dist/cli.js +42628 -42222
- package/dist/cli.js.map +4 -4
- package/dist/index.js +240 -68
- package/dist/index.js.map +2 -2
- package/dist/openclaw-entry.d.ts.map +1 -1
- package/dist/openclaw-entry.js +2 -1
- package/dist/openclaw-entry.js.map +2 -2
- package/dist/setup.d.ts +10 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45097,7 +45097,7 @@ function migratePersistedState(raw) {
|
|
|
45097
45097
|
};
|
|
45098
45098
|
}
|
|
45099
45099
|
var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
45100
|
-
//
|
|
45100
|
+
// 60s when idle
|
|
45101
45101
|
constructor(config) {
|
|
45102
45102
|
super();
|
|
45103
45103
|
this.config = config;
|
|
@@ -45119,9 +45119,15 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45119
45119
|
_stopped = false;
|
|
45120
45120
|
_persisted = null;
|
|
45121
45121
|
_httpServer = null;
|
|
45122
|
+
_pollFallbackTimer = null;
|
|
45123
|
+
_syncMessageIds = null;
|
|
45122
45124
|
static PING_INTERVAL_MS = 3e4;
|
|
45123
45125
|
// Send ping every 30s
|
|
45124
45126
|
static PING_TIMEOUT_MS = 1e4;
|
|
45127
|
+
// Treat as dead if no pong within 10s
|
|
45128
|
+
static POLL_FALLBACK_INTERVAL_MS = 3e4;
|
|
45129
|
+
// 30s when messages found
|
|
45130
|
+
static POLL_FALLBACK_IDLE_MS = 6e4;
|
|
45125
45131
|
get state() {
|
|
45126
45132
|
return this._state;
|
|
45127
45133
|
}
|
|
@@ -45200,30 +45206,50 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45200
45206
|
* Each session gets the same plaintext encrypted independently.
|
|
45201
45207
|
*/
|
|
45202
45208
|
async send(plaintext, options) {
|
|
45203
|
-
if (this._state
|
|
45209
|
+
if (this._state === "error" || this._state === "idle") {
|
|
45204
45210
|
throw new Error("Channel is not ready");
|
|
45205
45211
|
}
|
|
45212
|
+
if (this._sessions.size === 0) {
|
|
45213
|
+
throw new Error("No active sessions");
|
|
45214
|
+
}
|
|
45206
45215
|
const topicId = options?.topicId ?? this._persisted?.defaultTopicId;
|
|
45207
45216
|
this._appendHistory("agent", plaintext, topicId);
|
|
45208
45217
|
const messageGroupId = randomUUID();
|
|
45209
45218
|
for (const [convId, session] of this._sessions) {
|
|
45210
|
-
if (!session.activated)
|
|
45211
|
-
continue;
|
|
45212
|
-
}
|
|
45219
|
+
if (!session.activated) continue;
|
|
45213
45220
|
const encrypted = session.ratchet.encrypt(plaintext);
|
|
45214
45221
|
const transport = encryptedMessageToTransport(encrypted);
|
|
45215
|
-
|
|
45216
|
-
|
|
45217
|
-
|
|
45218
|
-
|
|
45219
|
-
|
|
45220
|
-
|
|
45221
|
-
|
|
45222
|
-
|
|
45223
|
-
|
|
45224
|
-
|
|
45225
|
-
|
|
45226
|
-
|
|
45222
|
+
const msg = {
|
|
45223
|
+
convId,
|
|
45224
|
+
headerBlob: transport.header_blob,
|
|
45225
|
+
ciphertext: transport.ciphertext,
|
|
45226
|
+
messageGroupId,
|
|
45227
|
+
topicId
|
|
45228
|
+
};
|
|
45229
|
+
if (this._state === "ready" && this._ws) {
|
|
45230
|
+
this._ws.send(
|
|
45231
|
+
JSON.stringify({
|
|
45232
|
+
event: "message",
|
|
45233
|
+
data: {
|
|
45234
|
+
conversation_id: msg.convId,
|
|
45235
|
+
header_blob: msg.headerBlob,
|
|
45236
|
+
ciphertext: msg.ciphertext,
|
|
45237
|
+
message_group_id: msg.messageGroupId,
|
|
45238
|
+
topic_id: msg.topicId
|
|
45239
|
+
}
|
|
45240
|
+
})
|
|
45241
|
+
);
|
|
45242
|
+
} else {
|
|
45243
|
+
if (!this._persisted.outboundQueue) {
|
|
45244
|
+
this._persisted.outboundQueue = [];
|
|
45245
|
+
}
|
|
45246
|
+
if (this._persisted.outboundQueue.length >= 50) {
|
|
45247
|
+
this._persisted.outboundQueue.shift();
|
|
45248
|
+
console.warn("[SecureChannel] Outbound queue full, dropping oldest message");
|
|
45249
|
+
}
|
|
45250
|
+
this._persisted.outboundQueue.push(msg);
|
|
45251
|
+
console.log(`[SecureChannel] Message queued (state=${this._state}, queue=${this._persisted.outboundQueue.length})`);
|
|
45252
|
+
}
|
|
45227
45253
|
}
|
|
45228
45254
|
await this._persistState();
|
|
45229
45255
|
}
|
|
@@ -45231,6 +45257,7 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45231
45257
|
this._stopped = true;
|
|
45232
45258
|
this._flushAcks();
|
|
45233
45259
|
this._stopPing();
|
|
45260
|
+
this._stopPollFallback();
|
|
45234
45261
|
this._stopHttpServer();
|
|
45235
45262
|
if (this._ackTimer) {
|
|
45236
45263
|
clearTimeout(this._ackTimer);
|
|
@@ -45569,6 +45596,7 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45569
45596
|
this._reconnectAttempt = 0;
|
|
45570
45597
|
this._startPing(ws);
|
|
45571
45598
|
await this._syncMissedMessages();
|
|
45599
|
+
await this._flushOutboundQueue();
|
|
45572
45600
|
this._setState("ready");
|
|
45573
45601
|
this.emit("ready");
|
|
45574
45602
|
});
|
|
@@ -45612,6 +45640,9 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45612
45640
|
*/
|
|
45613
45641
|
async _handleIncomingMessage(msgData) {
|
|
45614
45642
|
if (msgData.sender_device_id === this._deviceId) return;
|
|
45643
|
+
if (this._syncMessageIds?.has(msgData.message_id)) {
|
|
45644
|
+
return;
|
|
45645
|
+
}
|
|
45615
45646
|
const convId = msgData.conversation_id;
|
|
45616
45647
|
const session = this._sessions.get(convId);
|
|
45617
45648
|
if (!session) {
|
|
@@ -45936,67 +45967,87 @@ ${messageText}`;
|
|
|
45936
45967
|
* Sync missed messages across ALL sessions.
|
|
45937
45968
|
* For each conversation, fetches messages since last sync and decrypts.
|
|
45938
45969
|
*/
|
|
45970
|
+
/**
|
|
45971
|
+
* Paginated sync: fetch missed messages in pages of 200, up to 5 pages (1000 messages).
|
|
45972
|
+
* Tracks message IDs in _syncMessageIds to prevent duplicate processing from concurrent WS messages.
|
|
45973
|
+
*/
|
|
45939
45974
|
async _syncMissedMessages() {
|
|
45940
45975
|
if (!this._persisted?.lastMessageTimestamp || !this._deviceJwt) return;
|
|
45976
|
+
this._syncMessageIds = /* @__PURE__ */ new Set();
|
|
45977
|
+
const MAX_PAGES = 5;
|
|
45978
|
+
const PAGE_SIZE = 200;
|
|
45979
|
+
let since = this._persisted.lastMessageTimestamp;
|
|
45980
|
+
let totalProcessed = 0;
|
|
45941
45981
|
try {
|
|
45942
|
-
|
|
45943
|
-
|
|
45944
|
-
|
|
45945
|
-
|
|
45946
|
-
|
|
45947
|
-
|
|
45948
|
-
|
|
45949
|
-
|
|
45950
|
-
|
|
45951
|
-
|
|
45952
|
-
|
|
45953
|
-
|
|
45954
|
-
|
|
45955
|
-
)
|
|
45956
|
-
|
|
45957
|
-
|
|
45958
|
-
|
|
45959
|
-
|
|
45960
|
-
header_blob: msg.header_blob,
|
|
45961
|
-
ciphertext: msg.ciphertext
|
|
45962
|
-
});
|
|
45963
|
-
const plaintext = session.ratchet.decrypt(encrypted);
|
|
45964
|
-
this._sendAck(msg.id);
|
|
45965
|
-
if (!session.activated) {
|
|
45966
|
-
session.activated = true;
|
|
45967
|
-
console.log(`[SecureChannel] Session ${msg.conversation_id.slice(0, 8)}... activated during sync`);
|
|
45982
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
45983
|
+
const url = `${this.config.apiUrl}/api/v1/devices/${this._deviceId}/messages?since=${encodeURIComponent(since)}&limit=${PAGE_SIZE}`;
|
|
45984
|
+
const res = await fetch(url, {
|
|
45985
|
+
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
45986
|
+
});
|
|
45987
|
+
if (!res.ok) break;
|
|
45988
|
+
const messages = await res.json();
|
|
45989
|
+
if (messages.length === 0) break;
|
|
45990
|
+
for (const msg of messages) {
|
|
45991
|
+
if (msg.sender_device_id === this._deviceId) continue;
|
|
45992
|
+
if (this._syncMessageIds.has(msg.id)) continue;
|
|
45993
|
+
this._syncMessageIds.add(msg.id);
|
|
45994
|
+
const session = this._sessions.get(msg.conversation_id);
|
|
45995
|
+
if (!session) {
|
|
45996
|
+
console.warn(
|
|
45997
|
+
`[SecureChannel] No session for conversation ${msg.conversation_id} during sync, skipping`
|
|
45998
|
+
);
|
|
45999
|
+
continue;
|
|
45968
46000
|
}
|
|
45969
|
-
let messageText;
|
|
45970
|
-
let messageType;
|
|
45971
46001
|
try {
|
|
45972
|
-
const
|
|
45973
|
-
|
|
45974
|
-
|
|
45975
|
-
|
|
45976
|
-
|
|
45977
|
-
|
|
45978
|
-
|
|
45979
|
-
|
|
45980
|
-
|
|
45981
|
-
|
|
45982
|
-
|
|
45983
|
-
|
|
45984
|
-
|
|
45985
|
-
|
|
45986
|
-
|
|
45987
|
-
|
|
45988
|
-
|
|
45989
|
-
|
|
46002
|
+
const encrypted = transportToEncryptedMessage({
|
|
46003
|
+
header_blob: msg.header_blob,
|
|
46004
|
+
ciphertext: msg.ciphertext
|
|
46005
|
+
});
|
|
46006
|
+
const plaintext = session.ratchet.decrypt(encrypted);
|
|
46007
|
+
this._sendAck(msg.id);
|
|
46008
|
+
if (!session.activated) {
|
|
46009
|
+
session.activated = true;
|
|
46010
|
+
console.log(`[SecureChannel] Session ${msg.conversation_id.slice(0, 8)}... activated during sync`);
|
|
46011
|
+
}
|
|
46012
|
+
let messageText;
|
|
46013
|
+
let messageType;
|
|
46014
|
+
try {
|
|
46015
|
+
const parsed = JSON.parse(plaintext);
|
|
46016
|
+
messageType = parsed.type || "message";
|
|
46017
|
+
messageText = parsed.text || plaintext;
|
|
46018
|
+
} catch {
|
|
46019
|
+
messageType = "message";
|
|
46020
|
+
messageText = plaintext;
|
|
46021
|
+
}
|
|
46022
|
+
if (messageType === "message") {
|
|
46023
|
+
const topicId = msg.topic_id;
|
|
46024
|
+
this._appendHistory("owner", messageText, topicId);
|
|
46025
|
+
const metadata = {
|
|
46026
|
+
messageId: msg.id,
|
|
46027
|
+
conversationId: msg.conversation_id,
|
|
46028
|
+
timestamp: msg.created_at,
|
|
46029
|
+
topicId
|
|
46030
|
+
};
|
|
46031
|
+
this.emit("message", messageText, metadata);
|
|
46032
|
+
this.config.onMessage?.(messageText, metadata);
|
|
46033
|
+
}
|
|
46034
|
+
this._persisted.lastMessageTimestamp = msg.created_at;
|
|
46035
|
+
since = msg.created_at;
|
|
46036
|
+
totalProcessed++;
|
|
46037
|
+
} catch (err) {
|
|
46038
|
+
this.emit("error", err);
|
|
46039
|
+
break;
|
|
45990
46040
|
}
|
|
45991
|
-
this._persisted.lastMessageTimestamp = msg.created_at;
|
|
45992
|
-
} catch (err) {
|
|
45993
|
-
this.emit("error", err);
|
|
45994
|
-
break;
|
|
45995
46041
|
}
|
|
46042
|
+
await this._persistState();
|
|
46043
|
+
if (messages.length < PAGE_SIZE) break;
|
|
46044
|
+
}
|
|
46045
|
+
if (totalProcessed > 0) {
|
|
46046
|
+
console.log(`[SecureChannel] Synced ${totalProcessed} missed messages`);
|
|
45996
46047
|
}
|
|
45997
|
-
await this._persistState();
|
|
45998
46048
|
} catch {
|
|
45999
46049
|
}
|
|
46050
|
+
this._syncMessageIds = null;
|
|
46000
46051
|
}
|
|
46001
46052
|
_sendAck(messageId) {
|
|
46002
46053
|
this._pendingAcks.push(messageId);
|
|
@@ -46008,6 +46059,36 @@ ${messageText}`;
|
|
|
46008
46059
|
const batch = this._pendingAcks.splice(0, 50);
|
|
46009
46060
|
this._ws.send(JSON.stringify({ event: "ack", data: { message_ids: batch } }));
|
|
46010
46061
|
}
|
|
46062
|
+
async _flushOutboundQueue() {
|
|
46063
|
+
const queue = this._persisted?.outboundQueue;
|
|
46064
|
+
if (!queue || queue.length === 0 || !this._ws) return;
|
|
46065
|
+
console.log(`[SecureChannel] Flushing ${queue.length} queued outbound messages`);
|
|
46066
|
+
const messages = queue.splice(0);
|
|
46067
|
+
for (const msg of messages) {
|
|
46068
|
+
try {
|
|
46069
|
+
this._ws.send(
|
|
46070
|
+
JSON.stringify({
|
|
46071
|
+
event: "message",
|
|
46072
|
+
data: {
|
|
46073
|
+
conversation_id: msg.convId,
|
|
46074
|
+
header_blob: msg.headerBlob,
|
|
46075
|
+
ciphertext: msg.ciphertext,
|
|
46076
|
+
message_group_id: msg.messageGroupId,
|
|
46077
|
+
topic_id: msg.topicId
|
|
46078
|
+
}
|
|
46079
|
+
})
|
|
46080
|
+
);
|
|
46081
|
+
} catch (err) {
|
|
46082
|
+
if (!this._persisted.outboundQueue) {
|
|
46083
|
+
this._persisted.outboundQueue = [];
|
|
46084
|
+
}
|
|
46085
|
+
this._persisted.outboundQueue.push(msg);
|
|
46086
|
+
console.warn(`[SecureChannel] Failed to flush message, re-queued: ${err}`);
|
|
46087
|
+
break;
|
|
46088
|
+
}
|
|
46089
|
+
}
|
|
46090
|
+
await this._persistState();
|
|
46091
|
+
}
|
|
46011
46092
|
_startPing(ws) {
|
|
46012
46093
|
this._stopPing();
|
|
46013
46094
|
this._pingTimer = setInterval(() => {
|
|
@@ -46054,6 +46135,97 @@ ${messageText}`;
|
|
|
46054
46135
|
this._state = newState;
|
|
46055
46136
|
this.emit("state", newState);
|
|
46056
46137
|
this.config.onStateChange?.(newState);
|
|
46138
|
+
if (newState === "disconnected" && !this._pollFallbackTimer && this._deviceJwt) {
|
|
46139
|
+
this._startPollFallback();
|
|
46140
|
+
}
|
|
46141
|
+
if (newState === "ready" || newState === "error" || this._stopped) {
|
|
46142
|
+
this._stopPollFallback();
|
|
46143
|
+
}
|
|
46144
|
+
}
|
|
46145
|
+
_startPollFallback() {
|
|
46146
|
+
this._stopPollFallback();
|
|
46147
|
+
console.log("[SecureChannel] Starting HTTP poll fallback (WS is down)");
|
|
46148
|
+
let interval = _SecureChannel.POLL_FALLBACK_INTERVAL_MS;
|
|
46149
|
+
const poll = async () => {
|
|
46150
|
+
if (this._state === "ready" || this._stopped) {
|
|
46151
|
+
this._stopPollFallback();
|
|
46152
|
+
return;
|
|
46153
|
+
}
|
|
46154
|
+
try {
|
|
46155
|
+
const since = this._persisted?.lastMessageTimestamp;
|
|
46156
|
+
if (!since || !this._deviceJwt) return;
|
|
46157
|
+
const url = `${this.config.apiUrl}/api/v1/devices/${this._deviceId}/messages?since=${encodeURIComponent(since)}&limit=200`;
|
|
46158
|
+
const res = await fetch(url, {
|
|
46159
|
+
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
46160
|
+
});
|
|
46161
|
+
if (!res.ok) return;
|
|
46162
|
+
const messages = await res.json();
|
|
46163
|
+
let foundMessages = false;
|
|
46164
|
+
for (const msg of messages) {
|
|
46165
|
+
if (msg.sender_device_id === this._deviceId) continue;
|
|
46166
|
+
const session = this._sessions.get(msg.conversation_id);
|
|
46167
|
+
if (!session) continue;
|
|
46168
|
+
try {
|
|
46169
|
+
const encrypted = transportToEncryptedMessage({
|
|
46170
|
+
header_blob: msg.header_blob,
|
|
46171
|
+
ciphertext: msg.ciphertext
|
|
46172
|
+
});
|
|
46173
|
+
const plaintext = session.ratchet.decrypt(encrypted);
|
|
46174
|
+
if (!session.activated) {
|
|
46175
|
+
session.activated = true;
|
|
46176
|
+
}
|
|
46177
|
+
let messageText;
|
|
46178
|
+
let messageType;
|
|
46179
|
+
try {
|
|
46180
|
+
const parsed = JSON.parse(plaintext);
|
|
46181
|
+
messageType = parsed.type || "message";
|
|
46182
|
+
messageText = parsed.text || plaintext;
|
|
46183
|
+
} catch {
|
|
46184
|
+
messageType = "message";
|
|
46185
|
+
messageText = plaintext;
|
|
46186
|
+
}
|
|
46187
|
+
if (messageType === "message") {
|
|
46188
|
+
const topicId = msg.topic_id;
|
|
46189
|
+
this._appendHistory("owner", messageText, topicId);
|
|
46190
|
+
const metadata = {
|
|
46191
|
+
messageId: msg.id,
|
|
46192
|
+
conversationId: msg.conversation_id,
|
|
46193
|
+
timestamp: msg.created_at,
|
|
46194
|
+
topicId
|
|
46195
|
+
};
|
|
46196
|
+
this.emit("message", messageText, metadata);
|
|
46197
|
+
this.config.onMessage?.(messageText, metadata);
|
|
46198
|
+
foundMessages = true;
|
|
46199
|
+
}
|
|
46200
|
+
this._persisted.lastMessageTimestamp = msg.created_at;
|
|
46201
|
+
} catch (err) {
|
|
46202
|
+
this.emit("error", err);
|
|
46203
|
+
break;
|
|
46204
|
+
}
|
|
46205
|
+
}
|
|
46206
|
+
if (messages.length > 0) {
|
|
46207
|
+
await this._persistState();
|
|
46208
|
+
}
|
|
46209
|
+
const newInterval = foundMessages ? _SecureChannel.POLL_FALLBACK_INTERVAL_MS : _SecureChannel.POLL_FALLBACK_IDLE_MS;
|
|
46210
|
+
if (newInterval !== interval) {
|
|
46211
|
+
interval = newInterval;
|
|
46212
|
+
if (this._pollFallbackTimer) {
|
|
46213
|
+
clearInterval(this._pollFallbackTimer);
|
|
46214
|
+
this._pollFallbackTimer = setInterval(poll, interval);
|
|
46215
|
+
}
|
|
46216
|
+
}
|
|
46217
|
+
} catch {
|
|
46218
|
+
}
|
|
46219
|
+
};
|
|
46220
|
+
setTimeout(poll, 1e3);
|
|
46221
|
+
this._pollFallbackTimer = setInterval(poll, interval);
|
|
46222
|
+
}
|
|
46223
|
+
_stopPollFallback() {
|
|
46224
|
+
if (this._pollFallbackTimer) {
|
|
46225
|
+
clearInterval(this._pollFallbackTimer);
|
|
46226
|
+
this._pollFallbackTimer = null;
|
|
46227
|
+
console.log("[SecureChannel] Stopped HTTP poll fallback");
|
|
46228
|
+
}
|
|
46057
46229
|
}
|
|
46058
46230
|
_handleError(err) {
|
|
46059
46231
|
this._setState("error");
|