openclaw-app 1.0.7 → 1.0.8
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/index.ts +55 -7
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -241,6 +241,9 @@ interface RelayState {
|
|
|
241
241
|
* so multiple users can be active simultaneously without key collisions.
|
|
242
242
|
*/
|
|
243
243
|
e2eSessions: Map<string, E2EState>;
|
|
244
|
+
/** Previous E2E states kept for decrypting offline-buffered messages
|
|
245
|
+
* that were encrypted with the old key before the app reconnected. */
|
|
246
|
+
prevE2eSessions: Map<string, E2EState>;
|
|
244
247
|
relayToken: string;
|
|
245
248
|
}
|
|
246
249
|
|
|
@@ -261,6 +264,7 @@ function getRelayState(accountId: string): RelayState {
|
|
|
261
264
|
statusSink: null,
|
|
262
265
|
gatewayCtx: null,
|
|
263
266
|
e2eSessions: new Map(),
|
|
267
|
+
prevE2eSessions: new Map(),
|
|
264
268
|
relayToken: "",
|
|
265
269
|
};
|
|
266
270
|
relayStates.set(accountId, state);
|
|
@@ -555,8 +559,8 @@ function cleanupRelay(state: RelayState) {
|
|
|
555
559
|
try { state.ws.close(); } catch {}
|
|
556
560
|
state.ws = null;
|
|
557
561
|
}
|
|
558
|
-
// Clear all per-session E2E states — new handshakes needed on reconnect
|
|
559
562
|
state.e2eSessions.clear();
|
|
563
|
+
state.prevE2eSessions.clear();
|
|
560
564
|
}
|
|
561
565
|
|
|
562
566
|
function connectRelay(ctx: any, account: ResolvedAccount) {
|
|
@@ -663,13 +667,14 @@ async function handleRelayMessage(ctx: any, accountId: string, state: RelayState
|
|
|
663
667
|
ctx.log?.warn?.(`[${CHANNEL_ID}] [${accountId}] [E2E] peer_joined missing sessionKey, ignoring`);
|
|
664
668
|
return;
|
|
665
669
|
}
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
if (
|
|
670
|
-
|
|
671
|
-
|
|
670
|
+
// Preserve old E2E state for decrypting offline-buffered messages,
|
|
671
|
+
// then create a fresh state for the new handshake.
|
|
672
|
+
const oldE2E = state.e2eSessions.get(sessionKey);
|
|
673
|
+
if (oldE2E?.ready) {
|
|
674
|
+
state.prevE2eSessions.set(sessionKey, oldE2E);
|
|
675
|
+
ctx.log?.info?.(`[${CHANNEL_ID}] [${accountId}] [E2E] Session ${sessionKey} reconnected, old key preserved for pending_flush`);
|
|
672
676
|
}
|
|
677
|
+
state.e2eSessions.delete(sessionKey);
|
|
673
678
|
ctx.log?.info?.(`[${CHANNEL_ID}] [${accountId}] [E2E] App session joined (${sessionKey}), sending handshake`);
|
|
674
679
|
const sessionE2E = makeE2EState();
|
|
675
680
|
state.e2eSessions.set(sessionKey, sessionE2E);
|
|
@@ -700,6 +705,49 @@ async function handleRelayMessage(ctx: any, accountId: string, state: RelayState
|
|
|
700
705
|
return;
|
|
701
706
|
}
|
|
702
707
|
|
|
708
|
+
// Relay forwards buffered offline messages for re-encryption.
|
|
709
|
+
// Each message was encrypted with the old E2E key; we decrypt with the
|
|
710
|
+
// preserved old key and re-encrypt with the current (new) session key.
|
|
711
|
+
if (msg.type === "pending_flush") {
|
|
712
|
+
const sessionKey = msg.sessionKey as string | undefined;
|
|
713
|
+
const messages = msg.messages as string[] | undefined;
|
|
714
|
+
if (!sessionKey || !messages || messages.length === 0) {
|
|
715
|
+
ctx.log?.info?.(`[${CHANNEL_ID}] [${accountId}] [E2E] pending_flush: nothing to flush`);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const oldE2E = state.prevE2eSessions.get(sessionKey);
|
|
719
|
+
const newE2E = state.e2eSessions.get(sessionKey);
|
|
720
|
+
if (!oldE2E?.ready) {
|
|
721
|
+
ctx.log?.warn?.(`[${CHANNEL_ID}] [${accountId}] [E2E] pending_flush: no old key for session ${sessionKey}, dropping ${messages.length} message(s)`);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (!newE2E?.ready) {
|
|
725
|
+
ctx.log?.warn?.(`[${CHANNEL_ID}] [${accountId}] [E2E] pending_flush: new E2E not ready for session ${sessionKey}, dropping`);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
ctx.log?.info?.(`[${CHANNEL_ID}] [${accountId}] [E2E] pending_flush: re-encrypting ${messages.length} message(s) for session ${sessionKey}`);
|
|
729
|
+
for (const raw of messages) {
|
|
730
|
+
try {
|
|
731
|
+
const parsed = JSON.parse(raw);
|
|
732
|
+
if (parsed.type !== "encrypted" || !parsed.nonce || !parsed.ct) {
|
|
733
|
+
// Not encrypted — forward as-is with sessionKey
|
|
734
|
+
if (!parsed.sessionKey) parsed.sessionKey = sessionKey;
|
|
735
|
+
state.ws?.send(JSON.stringify(parsed));
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const plaintext = await e2eDecrypt(oldE2E, parsed.nonce, parsed.ct);
|
|
739
|
+
const reEncrypted = JSON.parse(await e2eEncrypt(newE2E, plaintext));
|
|
740
|
+
reEncrypted.sessionKey = sessionKey;
|
|
741
|
+
state.ws?.send(JSON.stringify(reEncrypted));
|
|
742
|
+
} catch (e) {
|
|
743
|
+
ctx.log?.warn?.(`[${CHANNEL_ID}] [${accountId}] [E2E] pending_flush: failed to re-encrypt: ${e}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// Old key no longer needed after flush
|
|
747
|
+
state.prevE2eSessions.delete(sessionKey);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
703
751
|
// App의 handshake 응답 수신 — ECDH 완성
|
|
704
752
|
if (msg.type === "handshake") {
|
|
705
753
|
const sessionKey = msg.sessionKey as string | undefined;
|
package/openclaw.plugin.json
CHANGED