@agentvault/secure-channel 0.6.15 → 0.6.17
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 +42633 -42221
- package/dist/cli.js.map +4 -4
- package/dist/index.js +246 -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);
|
|
@@ -45552,6 +45579,10 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45552
45579
|
}
|
|
45553
45580
|
_connect() {
|
|
45554
45581
|
if (this._stopped) return;
|
|
45582
|
+
if (this._reconnectTimer) {
|
|
45583
|
+
clearTimeout(this._reconnectTimer);
|
|
45584
|
+
this._reconnectTimer = null;
|
|
45585
|
+
}
|
|
45555
45586
|
if (this._ws) {
|
|
45556
45587
|
this._ws.removeAllListeners();
|
|
45557
45588
|
try {
|
|
@@ -45569,6 +45600,7 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45569
45600
|
this._reconnectAttempt = 0;
|
|
45570
45601
|
this._startPing(ws);
|
|
45571
45602
|
await this._syncMissedMessages();
|
|
45603
|
+
await this._flushOutboundQueue();
|
|
45572
45604
|
this._setState("ready");
|
|
45573
45605
|
this.emit("ready");
|
|
45574
45606
|
});
|
|
@@ -45612,6 +45644,9 @@ var SecureChannel = class _SecureChannel extends EventEmitter {
|
|
|
45612
45644
|
*/
|
|
45613
45645
|
async _handleIncomingMessage(msgData) {
|
|
45614
45646
|
if (msgData.sender_device_id === this._deviceId) return;
|
|
45647
|
+
if (this._syncMessageIds?.has(msgData.message_id)) {
|
|
45648
|
+
return;
|
|
45649
|
+
}
|
|
45615
45650
|
const convId = msgData.conversation_id;
|
|
45616
45651
|
const session = this._sessions.get(convId);
|
|
45617
45652
|
if (!session) {
|
|
@@ -45936,67 +45971,87 @@ ${messageText}`;
|
|
|
45936
45971
|
* Sync missed messages across ALL sessions.
|
|
45937
45972
|
* For each conversation, fetches messages since last sync and decrypts.
|
|
45938
45973
|
*/
|
|
45974
|
+
/**
|
|
45975
|
+
* Paginated sync: fetch missed messages in pages of 200, up to 5 pages (1000 messages).
|
|
45976
|
+
* Tracks message IDs in _syncMessageIds to prevent duplicate processing from concurrent WS messages.
|
|
45977
|
+
*/
|
|
45939
45978
|
async _syncMissedMessages() {
|
|
45940
45979
|
if (!this._persisted?.lastMessageTimestamp || !this._deviceJwt) return;
|
|
45980
|
+
this._syncMessageIds = /* @__PURE__ */ new Set();
|
|
45981
|
+
const MAX_PAGES = 5;
|
|
45982
|
+
const PAGE_SIZE = 200;
|
|
45983
|
+
let since = this._persisted.lastMessageTimestamp;
|
|
45984
|
+
let totalProcessed = 0;
|
|
45941
45985
|
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`);
|
|
45986
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
45987
|
+
const url = `${this.config.apiUrl}/api/v1/devices/${this._deviceId}/messages?since=${encodeURIComponent(since)}&limit=${PAGE_SIZE}`;
|
|
45988
|
+
const res = await fetch(url, {
|
|
45989
|
+
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
45990
|
+
});
|
|
45991
|
+
if (!res.ok) break;
|
|
45992
|
+
const messages = await res.json();
|
|
45993
|
+
if (messages.length === 0) break;
|
|
45994
|
+
for (const msg of messages) {
|
|
45995
|
+
if (msg.sender_device_id === this._deviceId) continue;
|
|
45996
|
+
if (this._syncMessageIds.has(msg.id)) continue;
|
|
45997
|
+
this._syncMessageIds.add(msg.id);
|
|
45998
|
+
const session = this._sessions.get(msg.conversation_id);
|
|
45999
|
+
if (!session) {
|
|
46000
|
+
console.warn(
|
|
46001
|
+
`[SecureChannel] No session for conversation ${msg.conversation_id} during sync, skipping`
|
|
46002
|
+
);
|
|
46003
|
+
continue;
|
|
45968
46004
|
}
|
|
45969
|
-
let messageText;
|
|
45970
|
-
let messageType;
|
|
45971
46005
|
try {
|
|
45972
|
-
const
|
|
45973
|
-
|
|
45974
|
-
|
|
45975
|
-
|
|
45976
|
-
|
|
45977
|
-
|
|
45978
|
-
|
|
45979
|
-
|
|
45980
|
-
|
|
45981
|
-
|
|
45982
|
-
|
|
45983
|
-
|
|
45984
|
-
|
|
45985
|
-
|
|
45986
|
-
|
|
45987
|
-
|
|
45988
|
-
|
|
45989
|
-
|
|
46006
|
+
const encrypted = transportToEncryptedMessage({
|
|
46007
|
+
header_blob: msg.header_blob,
|
|
46008
|
+
ciphertext: msg.ciphertext
|
|
46009
|
+
});
|
|
46010
|
+
const plaintext = session.ratchet.decrypt(encrypted);
|
|
46011
|
+
this._sendAck(msg.id);
|
|
46012
|
+
if (!session.activated) {
|
|
46013
|
+
session.activated = true;
|
|
46014
|
+
console.log(`[SecureChannel] Session ${msg.conversation_id.slice(0, 8)}... activated during sync`);
|
|
46015
|
+
}
|
|
46016
|
+
let messageText;
|
|
46017
|
+
let messageType;
|
|
46018
|
+
try {
|
|
46019
|
+
const parsed = JSON.parse(plaintext);
|
|
46020
|
+
messageType = parsed.type || "message";
|
|
46021
|
+
messageText = parsed.text || plaintext;
|
|
46022
|
+
} catch {
|
|
46023
|
+
messageType = "message";
|
|
46024
|
+
messageText = plaintext;
|
|
46025
|
+
}
|
|
46026
|
+
if (messageType === "message") {
|
|
46027
|
+
const topicId = msg.topic_id;
|
|
46028
|
+
this._appendHistory("owner", messageText, topicId);
|
|
46029
|
+
const metadata = {
|
|
46030
|
+
messageId: msg.id,
|
|
46031
|
+
conversationId: msg.conversation_id,
|
|
46032
|
+
timestamp: msg.created_at,
|
|
46033
|
+
topicId
|
|
46034
|
+
};
|
|
46035
|
+
this.emit("message", messageText, metadata);
|
|
46036
|
+
this.config.onMessage?.(messageText, metadata);
|
|
46037
|
+
}
|
|
46038
|
+
this._persisted.lastMessageTimestamp = msg.created_at;
|
|
46039
|
+
since = msg.created_at;
|
|
46040
|
+
totalProcessed++;
|
|
46041
|
+
} catch (err) {
|
|
46042
|
+
this.emit("error", err);
|
|
46043
|
+
break;
|
|
45990
46044
|
}
|
|
45991
|
-
this._persisted.lastMessageTimestamp = msg.created_at;
|
|
45992
|
-
} catch (err) {
|
|
45993
|
-
this.emit("error", err);
|
|
45994
|
-
break;
|
|
45995
46045
|
}
|
|
46046
|
+
await this._persistState();
|
|
46047
|
+
if (messages.length < PAGE_SIZE) break;
|
|
46048
|
+
}
|
|
46049
|
+
if (totalProcessed > 0) {
|
|
46050
|
+
console.log(`[SecureChannel] Synced ${totalProcessed} missed messages`);
|
|
45996
46051
|
}
|
|
45997
|
-
await this._persistState();
|
|
45998
46052
|
} catch {
|
|
45999
46053
|
}
|
|
46054
|
+
this._syncMessageIds = null;
|
|
46000
46055
|
}
|
|
46001
46056
|
_sendAck(messageId) {
|
|
46002
46057
|
this._pendingAcks.push(messageId);
|
|
@@ -46008,6 +46063,36 @@ ${messageText}`;
|
|
|
46008
46063
|
const batch = this._pendingAcks.splice(0, 50);
|
|
46009
46064
|
this._ws.send(JSON.stringify({ event: "ack", data: { message_ids: batch } }));
|
|
46010
46065
|
}
|
|
46066
|
+
async _flushOutboundQueue() {
|
|
46067
|
+
const queue = this._persisted?.outboundQueue;
|
|
46068
|
+
if (!queue || queue.length === 0 || !this._ws) return;
|
|
46069
|
+
console.log(`[SecureChannel] Flushing ${queue.length} queued outbound messages`);
|
|
46070
|
+
const messages = queue.splice(0);
|
|
46071
|
+
for (const msg of messages) {
|
|
46072
|
+
try {
|
|
46073
|
+
this._ws.send(
|
|
46074
|
+
JSON.stringify({
|
|
46075
|
+
event: "message",
|
|
46076
|
+
data: {
|
|
46077
|
+
conversation_id: msg.convId,
|
|
46078
|
+
header_blob: msg.headerBlob,
|
|
46079
|
+
ciphertext: msg.ciphertext,
|
|
46080
|
+
message_group_id: msg.messageGroupId,
|
|
46081
|
+
topic_id: msg.topicId
|
|
46082
|
+
}
|
|
46083
|
+
})
|
|
46084
|
+
);
|
|
46085
|
+
} catch (err) {
|
|
46086
|
+
if (!this._persisted.outboundQueue) {
|
|
46087
|
+
this._persisted.outboundQueue = [];
|
|
46088
|
+
}
|
|
46089
|
+
this._persisted.outboundQueue.push(msg);
|
|
46090
|
+
console.warn(`[SecureChannel] Failed to flush message, re-queued: ${err}`);
|
|
46091
|
+
break;
|
|
46092
|
+
}
|
|
46093
|
+
}
|
|
46094
|
+
await this._persistState();
|
|
46095
|
+
}
|
|
46011
46096
|
_startPing(ws) {
|
|
46012
46097
|
this._stopPing();
|
|
46013
46098
|
this._pingTimer = setInterval(() => {
|
|
@@ -46043,7 +46128,9 @@ ${messageText}`;
|
|
|
46043
46128
|
RECONNECT_MAX_MS
|
|
46044
46129
|
);
|
|
46045
46130
|
this._reconnectAttempt++;
|
|
46131
|
+
console.log(`[SecureChannel] Scheduling reconnect in ${delay}ms (attempt ${this._reconnectAttempt})`);
|
|
46046
46132
|
this._reconnectTimer = setTimeout(() => {
|
|
46133
|
+
this._reconnectTimer = null;
|
|
46047
46134
|
if (!this._stopped) {
|
|
46048
46135
|
this._connect();
|
|
46049
46136
|
}
|
|
@@ -46054,6 +46141,97 @@ ${messageText}`;
|
|
|
46054
46141
|
this._state = newState;
|
|
46055
46142
|
this.emit("state", newState);
|
|
46056
46143
|
this.config.onStateChange?.(newState);
|
|
46144
|
+
if (newState === "disconnected" && !this._pollFallbackTimer && this._deviceJwt) {
|
|
46145
|
+
this._startPollFallback();
|
|
46146
|
+
}
|
|
46147
|
+
if (newState === "ready" || newState === "error" || this._stopped) {
|
|
46148
|
+
this._stopPollFallback();
|
|
46149
|
+
}
|
|
46150
|
+
}
|
|
46151
|
+
_startPollFallback() {
|
|
46152
|
+
this._stopPollFallback();
|
|
46153
|
+
console.log("[SecureChannel] Starting HTTP poll fallback (WS is down)");
|
|
46154
|
+
let interval = _SecureChannel.POLL_FALLBACK_INTERVAL_MS;
|
|
46155
|
+
const poll = async () => {
|
|
46156
|
+
if (this._state === "ready" || this._stopped) {
|
|
46157
|
+
this._stopPollFallback();
|
|
46158
|
+
return;
|
|
46159
|
+
}
|
|
46160
|
+
try {
|
|
46161
|
+
const since = this._persisted?.lastMessageTimestamp;
|
|
46162
|
+
if (!since || !this._deviceJwt) return;
|
|
46163
|
+
const url = `${this.config.apiUrl}/api/v1/devices/${this._deviceId}/messages?since=${encodeURIComponent(since)}&limit=200`;
|
|
46164
|
+
const res = await fetch(url, {
|
|
46165
|
+
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
46166
|
+
});
|
|
46167
|
+
if (!res.ok) return;
|
|
46168
|
+
const messages = await res.json();
|
|
46169
|
+
let foundMessages = false;
|
|
46170
|
+
for (const msg of messages) {
|
|
46171
|
+
if (msg.sender_device_id === this._deviceId) continue;
|
|
46172
|
+
const session = this._sessions.get(msg.conversation_id);
|
|
46173
|
+
if (!session) continue;
|
|
46174
|
+
try {
|
|
46175
|
+
const encrypted = transportToEncryptedMessage({
|
|
46176
|
+
header_blob: msg.header_blob,
|
|
46177
|
+
ciphertext: msg.ciphertext
|
|
46178
|
+
});
|
|
46179
|
+
const plaintext = session.ratchet.decrypt(encrypted);
|
|
46180
|
+
if (!session.activated) {
|
|
46181
|
+
session.activated = true;
|
|
46182
|
+
}
|
|
46183
|
+
let messageText;
|
|
46184
|
+
let messageType;
|
|
46185
|
+
try {
|
|
46186
|
+
const parsed = JSON.parse(plaintext);
|
|
46187
|
+
messageType = parsed.type || "message";
|
|
46188
|
+
messageText = parsed.text || plaintext;
|
|
46189
|
+
} catch {
|
|
46190
|
+
messageType = "message";
|
|
46191
|
+
messageText = plaintext;
|
|
46192
|
+
}
|
|
46193
|
+
if (messageType === "message") {
|
|
46194
|
+
const topicId = msg.topic_id;
|
|
46195
|
+
this._appendHistory("owner", messageText, topicId);
|
|
46196
|
+
const metadata = {
|
|
46197
|
+
messageId: msg.id,
|
|
46198
|
+
conversationId: msg.conversation_id,
|
|
46199
|
+
timestamp: msg.created_at,
|
|
46200
|
+
topicId
|
|
46201
|
+
};
|
|
46202
|
+
this.emit("message", messageText, metadata);
|
|
46203
|
+
this.config.onMessage?.(messageText, metadata);
|
|
46204
|
+
foundMessages = true;
|
|
46205
|
+
}
|
|
46206
|
+
this._persisted.lastMessageTimestamp = msg.created_at;
|
|
46207
|
+
} catch (err) {
|
|
46208
|
+
this.emit("error", err);
|
|
46209
|
+
break;
|
|
46210
|
+
}
|
|
46211
|
+
}
|
|
46212
|
+
if (messages.length > 0) {
|
|
46213
|
+
await this._persistState();
|
|
46214
|
+
}
|
|
46215
|
+
const newInterval = foundMessages ? _SecureChannel.POLL_FALLBACK_INTERVAL_MS : _SecureChannel.POLL_FALLBACK_IDLE_MS;
|
|
46216
|
+
if (newInterval !== interval) {
|
|
46217
|
+
interval = newInterval;
|
|
46218
|
+
if (this._pollFallbackTimer) {
|
|
46219
|
+
clearInterval(this._pollFallbackTimer);
|
|
46220
|
+
this._pollFallbackTimer = setInterval(poll, interval);
|
|
46221
|
+
}
|
|
46222
|
+
}
|
|
46223
|
+
} catch {
|
|
46224
|
+
}
|
|
46225
|
+
};
|
|
46226
|
+
setTimeout(poll, 1e3);
|
|
46227
|
+
this._pollFallbackTimer = setInterval(poll, interval);
|
|
46228
|
+
}
|
|
46229
|
+
_stopPollFallback() {
|
|
46230
|
+
if (this._pollFallbackTimer) {
|
|
46231
|
+
clearInterval(this._pollFallbackTimer);
|
|
46232
|
+
this._pollFallbackTimer = null;
|
|
46233
|
+
console.log("[SecureChannel] Stopped HTTP poll fallback");
|
|
46234
|
+
}
|
|
46057
46235
|
}
|
|
46058
46236
|
_handleError(err) {
|
|
46059
46237
|
this._setState("error");
|