wowok 2.1.17 → 2.1.19
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/cjs/w/call/base.js +1 -0
- package/dist/cjs/w/call/base.js.map +1 -1
- package/dist/cjs/w/call/entity.js +1 -0
- package/dist/cjs/w/call/entity.js.map +1 -1
- package/dist/cjs/w/call/passport.js +1 -0
- package/dist/cjs/w/call/passport.js.map +1 -1
- package/dist/cjs/w/call/permission.js.map +1 -1
- package/dist/cjs/w/call/proof.js +1 -0
- package/dist/cjs/w/call/proof.js.map +1 -1
- package/dist/cjs/w/call/util.js +1 -0
- package/dist/cjs/w/call/util.js.map +1 -1
- package/dist/cjs/w/local/account.d.ts +3 -3
- package/dist/cjs/w/local/account.js +102 -29
- package/dist/cjs/w/local/account.js.map +1 -1
- package/dist/cjs/w/local/index.d.ts +1 -1
- package/dist/cjs/w/local/index.js +15 -6
- package/dist/cjs/w/local/index.js.map +1 -1
- package/dist/cjs/w/local/local.js +10 -5
- package/dist/cjs/w/local/local.js.map +1 -1
- package/dist/cjs/w/local/wip.js +1 -0
- package/dist/cjs/w/local/wip.js.map +1 -1
- package/dist/cjs/w/messenger/crypto.js +4 -4
- package/dist/cjs/w/messenger/crypto.js.map +1 -1
- package/dist/cjs/w/messenger/messenger-api.d.ts +1 -3
- package/dist/cjs/w/messenger/messenger-api.js +109 -56
- package/dist/cjs/w/messenger/messenger-api.js.map +1 -1
- package/dist/cjs/w/messenger/messenger-manager.d.ts +1 -6
- package/dist/cjs/w/messenger/messenger-manager.js +95 -150
- package/dist/cjs/w/messenger/messenger-manager.js.map +1 -1
- package/dist/cjs/w/messenger/messenger.d.ts +90 -31
- package/dist/cjs/w/messenger/messenger.js +517 -443
- package/dist/cjs/w/messenger/messenger.js.map +1 -1
- package/dist/cjs/w/messenger/server.d.ts +9 -1
- package/dist/cjs/w/messenger/server.js +56 -5
- package/dist/cjs/w/messenger/server.js.map +1 -1
- package/dist/cjs/w/messenger/session.d.ts +60 -10
- package/dist/cjs/w/messenger/session.js +492 -182
- package/dist/cjs/w/messenger/session.js.map +1 -1
- package/dist/cjs/w/messenger/storage.d.ts +30 -0
- package/dist/cjs/w/messenger/storage.js +143 -8
- package/dist/cjs/w/messenger/storage.js.map +1 -1
- package/dist/cjs/w/messenger/types.d.ts +37 -2
- package/dist/cjs/w/messenger/types.js +14 -3
- package/dist/cjs/w/messenger/types.js.map +1 -1
- package/dist/cjs/w/query/object.js +1 -0
- package/dist/cjs/w/query/object.js.map +1 -1
- package/dist/cjs/w/util.js +1 -0
- package/dist/cjs/w/util.js.map +1 -1
- package/dist/esm/w/call/base.js +1 -0
- package/dist/esm/w/call/base.js.map +1 -1
- package/dist/esm/w/call/entity.js +1 -0
- package/dist/esm/w/call/entity.js.map +1 -1
- package/dist/esm/w/call/passport.js +1 -0
- package/dist/esm/w/call/passport.js.map +1 -1
- package/dist/esm/w/call/permission.js.map +1 -1
- package/dist/esm/w/call/proof.js +1 -0
- package/dist/esm/w/call/proof.js.map +1 -1
- package/dist/esm/w/call/util.js +1 -0
- package/dist/esm/w/call/util.js.map +1 -1
- package/dist/esm/w/local/account.d.ts +3 -3
- package/dist/esm/w/local/account.js +102 -29
- package/dist/esm/w/local/account.js.map +1 -1
- package/dist/esm/w/local/index.d.ts +1 -1
- package/dist/esm/w/local/index.js +15 -6
- package/dist/esm/w/local/index.js.map +1 -1
- package/dist/esm/w/local/local.js +10 -5
- package/dist/esm/w/local/local.js.map +1 -1
- package/dist/esm/w/local/wip.js +1 -0
- package/dist/esm/w/local/wip.js.map +1 -1
- package/dist/esm/w/messenger/crypto.js +4 -4
- package/dist/esm/w/messenger/crypto.js.map +1 -1
- package/dist/esm/w/messenger/messenger-api.d.ts +1 -3
- package/dist/esm/w/messenger/messenger-api.js +109 -56
- package/dist/esm/w/messenger/messenger-api.js.map +1 -1
- package/dist/esm/w/messenger/messenger-manager.d.ts +1 -6
- package/dist/esm/w/messenger/messenger-manager.js +95 -150
- package/dist/esm/w/messenger/messenger-manager.js.map +1 -1
- package/dist/esm/w/messenger/messenger.d.ts +90 -31
- package/dist/esm/w/messenger/messenger.js +517 -443
- package/dist/esm/w/messenger/messenger.js.map +1 -1
- package/dist/esm/w/messenger/server.d.ts +9 -1
- package/dist/esm/w/messenger/server.js +56 -5
- package/dist/esm/w/messenger/server.js.map +1 -1
- package/dist/esm/w/messenger/session.d.ts +60 -10
- package/dist/esm/w/messenger/session.js +492 -182
- package/dist/esm/w/messenger/session.js.map +1 -1
- package/dist/esm/w/messenger/storage.d.ts +30 -0
- package/dist/esm/w/messenger/storage.js +143 -8
- package/dist/esm/w/messenger/storage.js.map +1 -1
- package/dist/esm/w/messenger/types.d.ts +37 -2
- package/dist/esm/w/messenger/types.js +14 -3
- package/dist/esm/w/messenger/types.js.map +1 -1
- package/dist/esm/w/query/object.js +1 -0
- package/dist/esm/w/query/object.js.map +1 -1
- package/dist/esm/w/util.js +1 -0
- package/dist/esm/w/util.js.map +1 -1
- package/package.json +1 -2
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* 3. 消息加密和解密
|
|
11
11
|
*/
|
|
12
12
|
import { KeyHelper, SessionBuilder, SessionCipher, SignalProtocolAddress, } from "libsignal-protocol-typescript";
|
|
13
|
-
import { Account } from "../local/account.js";
|
|
14
13
|
import { SignalProtocolStorage } from "./storage.js";
|
|
15
14
|
import { MessengerServerClient } from "./server.js";
|
|
16
15
|
import { recoverXed25519FromX25519PrivateKey, arrayBufferToUint8Array, bytesToBase64, base64ToBytes, } from "./crypto.js";
|
|
17
16
|
import { MessageType, DEFAULT_MESSENGER_CONFIG, MessengerError, MessengerErrorCode, DEFAULT_DEVICE_ID, } from "./types.js";
|
|
17
|
+
// 动态导入 Account 以避免循环依赖
|
|
18
|
+
// eslint-disable-next-line import/no-cycle
|
|
19
|
+
async function getAccount() {
|
|
20
|
+
// eslint-disable-next-line import/no-cycle
|
|
21
|
+
const { Account } = await import("../local/account.js");
|
|
22
|
+
return Account.Instance();
|
|
23
|
+
}
|
|
18
24
|
export class MessengerSession {
|
|
19
25
|
store;
|
|
20
26
|
serverClient;
|
|
@@ -53,7 +59,7 @@ export class MessengerSession {
|
|
|
53
59
|
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, "userAddress is required to derive identity");
|
|
54
60
|
}
|
|
55
61
|
// 从 Account 派生 X25519 密钥对
|
|
56
|
-
const { privateKey, publicKey } = await
|
|
62
|
+
const { privateKey, publicKey } = await (await getAccount()).deriveX25519KeyPair(userAddress);
|
|
57
63
|
// 生成注册 ID(可以从公钥派生,确保确定性)
|
|
58
64
|
const registrationId = this.deriveRegistrationId(publicKey);
|
|
59
65
|
// 转换为 Signal Protocol 格式
|
|
@@ -62,8 +68,8 @@ export class MessengerSession {
|
|
|
62
68
|
prefixedPublicKey[0] = 0x05; // Signal Protocol 标准前缀
|
|
63
69
|
prefixedPublicKey.set(publicKey, 1);
|
|
64
70
|
const signalIdentity = {
|
|
65
|
-
privKey: privateKey.
|
|
66
|
-
pubKey: prefixedPublicKey.
|
|
71
|
+
privKey: privateKey.slice().buffer,
|
|
72
|
+
pubKey: prefixedPublicKey.slice().buffer,
|
|
67
73
|
};
|
|
68
74
|
await this.store.setIdentity(signalIdentity, registrationId);
|
|
69
75
|
const xed25519 = recoverXed25519FromX25519PrivateKey(privateKey);
|
|
@@ -89,61 +95,153 @@ export class MessengerSession {
|
|
|
89
95
|
*/
|
|
90
96
|
async registerDevice(userAddress) {
|
|
91
97
|
const identity = await this.ensureIdentity(userAddress);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
const signedPreKeyId = 1;
|
|
99
|
+
let signedPreKey;
|
|
100
|
+
let signedPreKeyPublicKey;
|
|
101
|
+
let signedPreKeySignature;
|
|
102
|
+
let isNewKey = false;
|
|
103
|
+
// 【关键修复】使用带签名的存储方法
|
|
104
|
+
const existingSignedPreKey = await this.store.loadSignedPreKeyWithSignature(signedPreKeyId);
|
|
105
|
+
if (existingSignedPreKey) {
|
|
106
|
+
signedPreKeyPublicKey = arrayBufferToUint8Array(existingSignedPreKey.pubKey);
|
|
107
|
+
signedPreKeySignature = arrayBufferToUint8Array(existingSignedPreKey.signature);
|
|
108
|
+
signedPreKey = {
|
|
109
|
+
keyId: signedPreKeyId,
|
|
110
|
+
keyPair: {
|
|
111
|
+
pubKey: existingSignedPreKey.pubKey,
|
|
112
|
+
privKey: existingSignedPreKey.privKey,
|
|
113
|
+
},
|
|
114
|
+
signature: existingSignedPreKey.signature,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
signedPreKey = await KeyHelper.generateSignedPreKey(identity.signalIdentity, signedPreKeyId);
|
|
119
|
+
signedPreKeyPublicKey = arrayBufferToUint8Array(signedPreKey.keyPair.pubKey);
|
|
120
|
+
signedPreKeySignature = arrayBufferToUint8Array(signedPreKey.signature);
|
|
121
|
+
isNewKey = true;
|
|
122
|
+
}
|
|
123
|
+
// 生成 One-Time PreKeys(暂不保存)
|
|
124
|
+
const oneTimePrekeys = await this.generatePreKeyBatch(this.config.prekey_count, userAddress);
|
|
96
125
|
const prefixedIdentityKey = arrayBufferToUint8Array(identity.signalIdentity.pubKey);
|
|
97
|
-
|
|
98
|
-
const account = await Account.Instance().get(userAddress, false);
|
|
126
|
+
const account = await (await getAccount()).get(userAddress, false);
|
|
99
127
|
if (!account?.pubkey) {
|
|
100
128
|
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${userAddress}`);
|
|
101
129
|
}
|
|
102
|
-
// 构造签名
|
|
103
130
|
const timestamp = Date.now();
|
|
104
131
|
const nonce = this.generateNonce();
|
|
105
132
|
const message = `register:${account.pubkey}:${timestamp}:${nonce}`;
|
|
106
|
-
const signResult = await
|
|
133
|
+
const signResult = await (await getAccount()).signData(userAddress, message);
|
|
107
134
|
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
108
135
|
const registerRequest = {
|
|
109
136
|
userAddress,
|
|
110
137
|
deviceId: DEFAULT_DEVICE_ID,
|
|
111
138
|
registrationId: identity.registrationId,
|
|
112
139
|
identityKey: bytesToBase64(prefixedIdentityKey),
|
|
113
|
-
|
|
140
|
+
signedPrekey: {
|
|
141
|
+
keyId: signedPreKeyId,
|
|
142
|
+
publicKey: bytesToBase64(signedPreKeyPublicKey),
|
|
143
|
+
signature: bytesToBase64(signedPreKeySignature),
|
|
144
|
+
},
|
|
145
|
+
prekeys: oneTimePrekeys,
|
|
114
146
|
publicKey: account.pubkey,
|
|
115
147
|
signatureScheme: "ED25519",
|
|
116
148
|
signature: bytesToBase64(signature),
|
|
117
149
|
timestamp,
|
|
118
150
|
nonce,
|
|
119
151
|
};
|
|
152
|
+
console.log(`[Session Debug] Sending register request to server...`);
|
|
120
153
|
await this.serverClient.registerDevice(registerRequest);
|
|
154
|
+
console.log(`[Session Debug] Server register request successful!`);
|
|
155
|
+
console.log(`[Session Debug] Verifying registration complete...`);
|
|
156
|
+
await this.verifyRegistrationComplete(userAddress);
|
|
157
|
+
console.log(`[Session Debug] Registration verification complete!`);
|
|
158
|
+
// 【关键修复】只有是新 key 时才保存到本地数据库(包含签名)
|
|
159
|
+
if (isNewKey) {
|
|
160
|
+
await this.store.storeSignedPreKeyWithSignature(signedPreKeyId, signedPreKey.keyPair, signedPreKeySignature.buffer.slice(0));
|
|
161
|
+
}
|
|
121
162
|
}
|
|
122
163
|
/**
|
|
123
|
-
*
|
|
164
|
+
* 验证注册是否完成
|
|
165
|
+
* 轮询检查服务器是否已存在该用户
|
|
124
166
|
*/
|
|
125
|
-
async
|
|
167
|
+
async verifyRegistrationComplete(userAddress, maxRetries = 10, retryDelay = 500) {
|
|
168
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
169
|
+
try {
|
|
170
|
+
const status = await this.serverClient.getPrekeyStatus(userAddress);
|
|
171
|
+
// 如果成功获取状态,说明注册已完成
|
|
172
|
+
console.log(`Registration verified for ${userAddress}, prekeys: ${status.currentCount}/${status.maxAllowed}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// 如果是 404,说明注册还没完成,继续等待
|
|
177
|
+
if (error?.message?.includes("404") || error?.status === 404) {
|
|
178
|
+
if (i < maxRetries - 1) {
|
|
179
|
+
console.log(`Waiting for registration to complete... (${i + 1}/${maxRetries})`);
|
|
180
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// 其他错误,抛出
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
throw new MessengerError(MessengerErrorCode.REGISTRATION_FAILED, `Registration verification failed after ${maxRetries} attempts`);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 检查设备是否已在服务器上注册
|
|
192
|
+
* 通过查询服务器上的预密钥状态来判断
|
|
193
|
+
*/
|
|
194
|
+
async isDeviceRegistered(userAddress) {
|
|
195
|
+
try {
|
|
196
|
+
// 检查本地是否有 signedPreKey(使用带签名的方法)
|
|
197
|
+
const signedPreKey = await this.store.loadSignedPreKeyWithSignature(1);
|
|
198
|
+
if (!signedPreKey) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
// 尝试从服务器获取预密钥状态
|
|
202
|
+
const _status = await this.serverClient.getPrekeyStatus(userAddress);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
// 如果是 404,说明设备未注册
|
|
207
|
+
if (error?.message?.includes("404") || error?.status === 404) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 生成预密钥批次(One-Time PreKeys)
|
|
215
|
+
*/
|
|
216
|
+
async generatePreKeyBatch(count, userAddress) {
|
|
126
217
|
if (count <= 0)
|
|
127
218
|
return [];
|
|
128
|
-
const identity = await this.ensureIdentity();
|
|
129
219
|
const meta = await this.store.getMeta();
|
|
130
220
|
let nextPreKeyId = meta.nextPreKeyId || 1;
|
|
131
221
|
const prekeys = [];
|
|
132
222
|
for (let i = 0; i < count; i++) {
|
|
133
223
|
const keyId = nextPreKeyId + i;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
224
|
+
const existingPreKey = await this.store.loadPreKey(keyId);
|
|
225
|
+
if (existingPreKey) {
|
|
226
|
+
const pubKeyBytes = new Uint8Array(existingPreKey.pubKey);
|
|
227
|
+
const publicKeyBase64 = bytesToBase64(pubKeyBytes);
|
|
228
|
+
const signResult = await (await getAccount()).signData(userAddress, pubKeyBytes);
|
|
229
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
230
|
+
prekeys.push({
|
|
231
|
+
keyId,
|
|
232
|
+
publicKey: publicKeyBase64,
|
|
233
|
+
signature: bytesToBase64(signature),
|
|
234
|
+
});
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const preKey = await KeyHelper.generatePreKey(keyId);
|
|
238
|
+
const pubKeyBytes = new Uint8Array(preKey.keyPair.pubKey);
|
|
239
|
+
const signResult = await (await getAccount()).signData(userAddress, pubKeyBytes);
|
|
240
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
241
|
+
await this.store.storePreKey(keyId, preKey.keyPair);
|
|
144
242
|
prekeys.push({
|
|
145
243
|
keyId,
|
|
146
|
-
publicKey: bytesToBase64(
|
|
244
|
+
publicKey: bytesToBase64(pubKeyBytes),
|
|
147
245
|
signature: bytesToBase64(signature),
|
|
148
246
|
});
|
|
149
247
|
}
|
|
@@ -153,44 +251,30 @@ export class MessengerSession {
|
|
|
153
251
|
}
|
|
154
252
|
/**
|
|
155
253
|
* 确保服务器上有足够可用的预密钥
|
|
156
|
-
*
|
|
157
|
-
* 逻辑:查询服务器当前预密钥数量,按需生成并上传,补充到服务器最大容量
|
|
158
254
|
*/
|
|
159
255
|
async ensurePreKeys(userAddress, force = false) {
|
|
160
|
-
// 1. 查询服务器当前预密钥状态
|
|
161
256
|
const status = await this.serverClient.getPrekeyStatus(userAddress);
|
|
162
|
-
// 2. 如果服务器已满且非强制模式,直接返回
|
|
163
257
|
if (!force && status.currentCount >= status.maxAllowed) {
|
|
164
|
-
|
|
258
|
+
return;
|
|
165
259
|
}
|
|
166
|
-
// 3. 计算需要补充的数量
|
|
167
260
|
const needCount = status.maxAllowed - status.currentCount;
|
|
168
261
|
if (needCount <= 0) {
|
|
169
|
-
/*console.log(
|
|
170
|
-
`No need to refill prekeys, server has ${status.currentCount}/${status.maxAllowed}`,
|
|
171
|
-
);*/
|
|
172
262
|
return;
|
|
173
263
|
}
|
|
174
|
-
/*console.log(
|
|
175
|
-
`Refilling prekeys: server has ${status.currentCount}/${status.maxAllowed}, need ${needCount}`,
|
|
176
|
-
);*/
|
|
177
|
-
// 4. 确保身份已初始化
|
|
178
264
|
await this.ensureIdentity(userAddress);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
265
|
+
const prekeys = await this.generatePreKeyBatch(needCount, userAddress);
|
|
266
|
+
if (prekeys.length === 0) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const account = await (await getAccount()).get(userAddress, false);
|
|
183
270
|
if (!account?.pubkey) {
|
|
184
271
|
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${userAddress}`);
|
|
185
272
|
}
|
|
186
|
-
// 7. 构造上传请求(包含签名)
|
|
187
|
-
// 8. 生成签名
|
|
188
273
|
const timestamp = Date.now();
|
|
189
274
|
const nonce = this.generateNonce();
|
|
190
275
|
const message = `upload_prekeys:${account.pubkey}:${timestamp}:${nonce}`;
|
|
191
|
-
const signResult = await
|
|
276
|
+
const signResult = await (await getAccount()).signData(userAddress, message);
|
|
192
277
|
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
193
|
-
// 9. 上传预密钥(服务器会自动丢弃超过最大数量的部分)
|
|
194
278
|
await this.serverClient.uploadPreKeys({
|
|
195
279
|
userAddress,
|
|
196
280
|
prekeys,
|
|
@@ -212,185 +296,255 @@ export class MessengerSession {
|
|
|
212
296
|
/**
|
|
213
297
|
* 建立与对方的会话
|
|
214
298
|
*/
|
|
215
|
-
async establishSession(myAddress, peerAddress, peerDeviceId = DEFAULT_DEVICE_ID) {
|
|
299
|
+
async establishSession(myAddress, peerAddress, peerDeviceId = DEFAULT_DEVICE_ID, remoteBundle) {
|
|
216
300
|
const protocolAddress = new SignalProtocolAddress(peerAddress, peerDeviceId);
|
|
217
|
-
// 检查是否已有会话
|
|
218
301
|
const existingSession = await this.store.loadSession(protocolAddress.toString());
|
|
219
302
|
if (existingSession) {
|
|
220
303
|
return;
|
|
221
304
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
305
|
+
if (!remoteBundle) {
|
|
306
|
+
const account = await (await getAccount()).get(myAddress, false);
|
|
307
|
+
if (!account?.pubkey) {
|
|
308
|
+
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${myAddress}`);
|
|
309
|
+
}
|
|
310
|
+
const timestamp = Date.now();
|
|
311
|
+
const nonce = this.generateNonce();
|
|
312
|
+
const publicKeyWithFlag = account.pubkey;
|
|
313
|
+
const message = `get_bundle:${myAddress}:${timestamp}:${nonce}`;
|
|
314
|
+
const signResult = await (await getAccount()).signData(myAddress, message);
|
|
315
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
316
|
+
remoteBundle = await this.serverClient.fetchRemoteBundle(peerAddress, myAddress, publicKeyWithFlag, {
|
|
317
|
+
signatureScheme: "ED25519",
|
|
318
|
+
signature: bytesToBase64(new Uint8Array(signature)),
|
|
319
|
+
timestamp,
|
|
320
|
+
nonce,
|
|
321
|
+
}, peerDeviceId);
|
|
226
322
|
}
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
const publicKeyWithFlag = account.pubkey;
|
|
231
|
-
// 签名格式: get_bundle:{address}:{timestamp}:{nonce}
|
|
232
|
-
const message = `get_bundle:${myAddress}:${timestamp}:${nonce}`;
|
|
233
|
-
const signResult = await Account.Instance().signData(myAddress, message);
|
|
234
|
-
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
235
|
-
// 获取对方密钥包(带签名验证)
|
|
236
|
-
const remoteBundle = await this.serverClient.fetchRemoteBundle(peerAddress, myAddress, publicKeyWithFlag, {
|
|
237
|
-
signatureScheme: "ED25519",
|
|
238
|
-
signature: bytesToBase64(new Uint8Array(signature)),
|
|
239
|
-
timestamp,
|
|
240
|
-
nonce,
|
|
241
|
-
}, peerDeviceId);
|
|
242
|
-
const preferredPreKey = remoteBundle.signedPrekey;
|
|
243
|
-
if (!preferredPreKey) {
|
|
323
|
+
const oneTimePreKey = remoteBundle.oneTimePrekey;
|
|
324
|
+
const signedPreKey = remoteBundle.signedPrekey;
|
|
325
|
+
if (!signedPreKey) {
|
|
244
326
|
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `No signed prekey available for ${peerAddress}`);
|
|
245
327
|
}
|
|
246
|
-
// 转换为 Signal Protocol 设备格式
|
|
247
328
|
const identityKeyBytes = base64ToBytes(remoteBundle.identityKey);
|
|
248
|
-
const preKeyPublicBytes = base64ToBytes(preferredPreKey.publicKey);
|
|
249
|
-
const signatureBytes = base64ToBytes(preferredPreKey.signature);
|
|
250
|
-
// 注意:identityKey 和 signedPreKey.publicKey 都不要 decode 去掉前缀!
|
|
251
|
-
// 因为当初签名时用的就是带 0x05 前缀的原始公钥!
|
|
252
329
|
const originalIdentityKeyWithPrefix = new Uint8Array(identityKeyBytes);
|
|
253
|
-
const
|
|
330
|
+
const signedPreKeyPublicBytes = base64ToBytes(signedPreKey.publicKey);
|
|
331
|
+
const signedPreKeySignatureBytes = base64ToBytes(signedPreKey.signature);
|
|
332
|
+
const originalSignedPreKeyWithPrefix = new Uint8Array(signedPreKeyPublicBytes);
|
|
333
|
+
let oneTimePreKeyData = undefined;
|
|
334
|
+
if (oneTimePreKey) {
|
|
335
|
+
const oneTimePreKeyPublicBytes = base64ToBytes(oneTimePreKey.publicKey);
|
|
336
|
+
const originalOneTimePreKeyWithPrefix = new Uint8Array(oneTimePreKeyPublicBytes);
|
|
337
|
+
oneTimePreKeyData = {
|
|
338
|
+
keyId: oneTimePreKey.keyId,
|
|
339
|
+
publicKey: originalOneTimePreKeyWithPrefix.buffer.slice(originalOneTimePreKeyWithPrefix.byteOffset, originalOneTimePreKeyWithPrefix.byteOffset +
|
|
340
|
+
originalOneTimePreKeyWithPrefix.byteLength),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
254
343
|
const device = {
|
|
255
344
|
registrationId: remoteBundle.registrationId,
|
|
256
345
|
identityKey: originalIdentityKeyWithPrefix.buffer.slice(originalIdentityKeyWithPrefix.byteOffset, originalIdentityKeyWithPrefix.byteOffset +
|
|
257
346
|
originalIdentityKeyWithPrefix.byteLength),
|
|
258
347
|
signedPreKey: {
|
|
259
|
-
keyId:
|
|
260
|
-
publicKey:
|
|
261
|
-
|
|
262
|
-
signature: new Uint8Array(
|
|
348
|
+
keyId: signedPreKey.keyId,
|
|
349
|
+
publicKey: originalSignedPreKeyWithPrefix.buffer.slice(originalSignedPreKeyWithPrefix.byteOffset, originalSignedPreKeyWithPrefix.byteOffset +
|
|
350
|
+
originalSignedPreKeyWithPrefix.byteLength),
|
|
351
|
+
signature: new Uint8Array(signedPreKeySignatureBytes).buffer.slice(signedPreKeySignatureBytes.byteOffset, signedPreKeySignatureBytes.byteOffset +
|
|
352
|
+
signedPreKeySignatureBytes.byteLength),
|
|
263
353
|
},
|
|
264
|
-
|
|
265
|
-
// 这是可以的,Signal Protocol 支持仅使用 signedPreKey 建立会话
|
|
266
|
-
preKey: undefined,
|
|
354
|
+
preKey: oneTimePreKeyData,
|
|
267
355
|
};
|
|
268
|
-
// 建立会话
|
|
269
356
|
const builder = new SessionBuilder(this.store, protocolAddress);
|
|
270
357
|
await builder.processPreKey(device);
|
|
271
|
-
// 显式存储对方的身份密钥,以便后续解密对方的回复
|
|
272
|
-
// 使用 protocolAddress.toString() 作为键(包含 deviceId)
|
|
273
358
|
await this.store.saveIdentity(protocolAddress.toString(), device.identityKey);
|
|
274
359
|
}
|
|
275
360
|
/**
|
|
276
361
|
* 加密消息
|
|
277
|
-
* @param myAddress 发送方地址
|
|
278
|
-
* @param peerAddress 接收方地址
|
|
279
|
-
* @param plaintext 明文
|
|
280
|
-
* @param peerDeviceId 设备ID
|
|
281
|
-
* @returns 加密后的消息(type 表示底层 Signal Protocol 消息类型:3=PreKeyMessage, 1=WhisperMessage)
|
|
282
362
|
*/
|
|
283
363
|
async encryptMessage(myAddress, peerAddress, plaintext, peerDeviceId = DEFAULT_DEVICE_ID) {
|
|
284
364
|
const protocolAddress = new SignalProtocolAddress(peerAddress, peerDeviceId);
|
|
285
|
-
// 检查是否已有会话(用于确定底层消息类型)
|
|
286
365
|
const sessionKey = protocolAddress.toString();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
366
|
+
let existingSession = await this.store.loadSession(sessionKey);
|
|
367
|
+
let isNewSession = !existingSession;
|
|
368
|
+
let retryCount = 0;
|
|
369
|
+
const maxRetries = 5; // 【用户要求】加大容错窗口,让消息可以重试更多次
|
|
370
|
+
while (retryCount <= maxRetries) {
|
|
371
|
+
try {
|
|
372
|
+
if (isNewSession) {
|
|
373
|
+
console.log(`[Session] 建立新会话 (尝试 ${retryCount + 1}/${maxRetries + 1})`);
|
|
374
|
+
const account = await (await getAccount()).get(myAddress, false);
|
|
375
|
+
if (!account?.pubkey) {
|
|
376
|
+
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${myAddress}`);
|
|
377
|
+
}
|
|
378
|
+
const timestamp = Date.now();
|
|
379
|
+
const nonce = this.generateNonce();
|
|
380
|
+
const publicKeyWithFlag = account.pubkey;
|
|
381
|
+
const message = `get_bundle:${myAddress}:${timestamp}:${nonce}`;
|
|
382
|
+
const signResult = await (await getAccount()).signData(myAddress, message);
|
|
383
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
384
|
+
const remoteBundle = await this.serverClient.fetchRemoteBundle(peerAddress, myAddress, publicKeyWithFlag, {
|
|
385
|
+
signatureScheme: "ED25519",
|
|
386
|
+
signature: bytesToBase64(new Uint8Array(signature)),
|
|
387
|
+
timestamp,
|
|
388
|
+
nonce,
|
|
389
|
+
}, peerDeviceId);
|
|
390
|
+
await this.establishSession(myAddress, peerAddress, peerDeviceId, remoteBundle);
|
|
391
|
+
}
|
|
392
|
+
const cipher = new SessionCipher(this.store, protocolAddress);
|
|
393
|
+
const encoder = new TextEncoder();
|
|
394
|
+
const cipherMessage = await cipher.encrypt(encoder.encode(plaintext).buffer);
|
|
395
|
+
let bodyBuffer;
|
|
396
|
+
if (typeof cipherMessage.body === "string") {
|
|
397
|
+
const bodyBytes = Buffer.from(cipherMessage.body, "binary");
|
|
398
|
+
bodyBuffer = new Uint8Array(bodyBytes).buffer;
|
|
399
|
+
}
|
|
400
|
+
else if (cipherMessage.body) {
|
|
401
|
+
bodyBuffer = cipherMessage.body;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
throw new MessengerError(MessengerErrorCode.ENCRYPTION_FAILED, "Cipher message body is empty");
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
type: cipherMessage.type,
|
|
408
|
+
body: bodyBuffer,
|
|
409
|
+
registrationId: cipherMessage.registrationId,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
console.error(`[Session] 加密失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`, error);
|
|
414
|
+
if (retryCount >= maxRetries) {
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
console.log(`[Session] 尝试删除旧会话并重新建立...`);
|
|
418
|
+
try {
|
|
419
|
+
if (existingSession) {
|
|
420
|
+
await this.store.removeSession(sessionKey);
|
|
421
|
+
existingSession = undefined;
|
|
422
|
+
}
|
|
423
|
+
isNewSession = true;
|
|
424
|
+
retryCount++;
|
|
425
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
426
|
+
}
|
|
427
|
+
catch (rebuildError) {
|
|
428
|
+
console.error(`[Session] 重新建立会话失败:`, rebuildError);
|
|
429
|
+
throw error;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
307
432
|
}
|
|
308
|
-
|
|
309
|
-
// SessionCipher 根据 session.pendingPreKey 是否存在来决定消息类型
|
|
310
|
-
/*const msgType = cipherMessage.type === 3 ? "PREKEY" : "WHISPER";
|
|
311
|
-
console.log(
|
|
312
|
-
`[Session Debug] 加密完成: msgType=${msgType}(${cipherMessage.type}), bodyLength=${bodyBuffer.byteLength}, isNewSession=${isNewSession}`,
|
|
313
|
-
);*/
|
|
314
|
-
return {
|
|
315
|
-
type: cipherMessage.type,
|
|
316
|
-
body: bodyBuffer,
|
|
317
|
-
registrationId: cipherMessage.registrationId,
|
|
318
|
-
};
|
|
433
|
+
throw new MessengerError(MessengerErrorCode.ENCRYPTION_FAILED, "Encryption failed after multiple attempts");
|
|
319
434
|
}
|
|
320
435
|
/**
|
|
321
436
|
* 解密消息
|
|
322
437
|
*/
|
|
323
438
|
async decryptMessage(myAddress, peerAddress, ciphertext, msgType, peerDeviceId = DEFAULT_DEVICE_ID) {
|
|
324
439
|
const protocolAddress = new SignalProtocolAddress(peerAddress, peerDeviceId);
|
|
325
|
-
// Signal 协议中,接收方直接使用自己的私钥解密消息
|
|
326
|
-
// decryptPreKeyWhisperMessage 会自动处理会话建立,不需要预先获取发送方的密钥包
|
|
327
|
-
// 发送方使用接收方的预密钥公钥加密,接收方使用自己的预密钥私钥解密
|
|
328
|
-
const cipher = new SessionCipher(this.store, protocolAddress);
|
|
329
|
-
const decoder = new TextDecoder();
|
|
330
|
-
// 检查会话状态
|
|
331
440
|
const sessionKey = protocolAddress.toString();
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const ciphertextArray = new Uint8Array(ciphertext);
|
|
441
|
+
let existingSession = await this.store.loadSession(sessionKey);
|
|
442
|
+
const decoder = new TextDecoder();
|
|
335
443
|
let ciphertextBinary = "";
|
|
444
|
+
const ciphertextArray = new Uint8Array(ciphertext);
|
|
336
445
|
for (let i = 0; i < ciphertextArray.length; i++) {
|
|
337
446
|
ciphertextBinary += String.fromCharCode(ciphertextArray[i]);
|
|
338
447
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
448
|
+
let retryCount = 0;
|
|
449
|
+
const maxRetries = 5; // 【用户要求】加大容错窗口,让消息可以重试更多次
|
|
450
|
+
// 【新增】如果是 WHISPER_MESSAGE 且没有现有会话,直接抛出可重试错误
|
|
451
|
+
if (msgType !== MessageType.PREKEY_MESSAGE && !existingSession) {
|
|
452
|
+
console.log(`[Session] 收到 WHISPER_MESSAGE 但无现有会话,需要等待 PREKEY_MESSAGE`);
|
|
453
|
+
throw new Error("收到 WHISPER_MESSAGE 但无现有会话,需要等待 PREKEY_MESSAGE");
|
|
454
|
+
}
|
|
455
|
+
while (retryCount <= maxRetries) {
|
|
456
|
+
try {
|
|
457
|
+
const cipher = new SessionCipher(this.store, protocolAddress);
|
|
458
|
+
let plaintextBuffer;
|
|
459
|
+
if (msgType === MessageType.PREKEY_MESSAGE) {
|
|
460
|
+
console.log(`[Session] 收到 PREKEY_MESSAGE,尝试解密 (尝试 ${retryCount + 1}/${maxRetries + 1})`);
|
|
461
|
+
try {
|
|
462
|
+
// 【用户要求】先用尝试解密(真解密可能推动会话密钥变更,极度重要!)
|
|
463
|
+
plaintextBuffer =
|
|
464
|
+
await cipher.decryptPreKeyWhisperMessage(ciphertextBinary, "binary");
|
|
465
|
+
console.log(`[Session] PREKEY_MESSAGE 解密成功!`);
|
|
466
|
+
return decoder.decode(plaintextBuffer);
|
|
467
|
+
}
|
|
468
|
+
catch (decryptError) {
|
|
469
|
+
console.log(`[Session] 直接解密 PREKEY_MESSAGE 失败: ${decryptError instanceof Error ? decryptError.message : String(decryptError)}`);
|
|
470
|
+
// 如果解密失败,再处理 PREKEY 竞争的情况(但只在第一次尝试时处理)
|
|
471
|
+
if (existingSession && retryCount === 0) {
|
|
472
|
+
// 【用户要求】PREKEY 竞争时,双方始终优先以小地址发送的消息为最终依据
|
|
473
|
+
const myAddr = myAddress.toLowerCase();
|
|
474
|
+
const peerAddr = peerAddress.toLowerCase();
|
|
475
|
+
if (myAddr < peerAddr) {
|
|
476
|
+
// 我的地址较小,保留我的会话!不处理这个 PREKEY_MESSAGE,稍后重试
|
|
477
|
+
console.log(`[Session] 我的地址较小 (${myAddr} < ${peerAddr}),保留我的会话,稍后重试`);
|
|
478
|
+
throw new Error("PREKEY 竞争:我的地址较小,保留我的会话");
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
// 我的地址较大,删除我的会话,用对方的 PREKEY_MESSAGE 建立新会话
|
|
482
|
+
console.log(`[Session] 我的地址较大 (${myAddr} > ${peerAddr}),删除我的会话,用对方的 PREKEY_MESSAGE 建立新会话`);
|
|
483
|
+
await this.store.removeSession(sessionKey);
|
|
484
|
+
existingSession = undefined;
|
|
485
|
+
// 现在删除了会话,重新尝试解密
|
|
486
|
+
console.log(`[Session] 已删除旧会话,重新尝试解密...`);
|
|
487
|
+
plaintextBuffer =
|
|
488
|
+
await cipher.decryptPreKeyWhisperMessage(ciphertextBinary, "binary");
|
|
489
|
+
console.log(`[Session] PREKEY_MESSAGE 解密成功,新会话已建立`);
|
|
490
|
+
return decoder.decode(plaintextBuffer);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
// 没有现有会话,或者不是第一次尝试,重新抛出解密错误
|
|
495
|
+
throw decryptError;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
348
498
|
}
|
|
349
499
|
else {
|
|
350
|
-
plaintextBuffer = await cipher.
|
|
500
|
+
plaintextBuffer = await cipher.decryptWhisperMessage(ciphertextBinary, "binary");
|
|
351
501
|
}
|
|
502
|
+
return decoder.decode(plaintextBuffer);
|
|
352
503
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
console.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
console.
|
|
363
|
-
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
`
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
504
|
+
catch (error) {
|
|
505
|
+
console.error(`[Session Debug] 解密失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`);
|
|
506
|
+
console.error(` 错误类型: ${error instanceof Error ? error.constructor.name : typeof error}`);
|
|
507
|
+
console.error(` 错误消息: ${error instanceof Error ? error.message : String(error)}`);
|
|
508
|
+
if (error instanceof Error && error.stack) {
|
|
509
|
+
console.error(` 堆栈: ${error.stack.split("\n")[0]}`);
|
|
510
|
+
}
|
|
511
|
+
console.error(` 上下文:`);
|
|
512
|
+
console.error(` - myAddress: ${myAddress}`);
|
|
513
|
+
console.error(` - peerAddress: ${peerAddress}`);
|
|
514
|
+
console.error(` - msgType: ${msgType} (${msgType === MessageType.PREKEY_MESSAGE ? "PREKEY" : "WHISPER"})`);
|
|
515
|
+
console.error(` - sessionKey: ${sessionKey}`);
|
|
516
|
+
console.error(` - existingSession: ${existingSession ? "存在" : "不存在"}`);
|
|
517
|
+
console.error(` - ciphertext.length: ${ciphertext.byteLength}`);
|
|
518
|
+
const identityKey = await this.store.getIdentityKeyPair();
|
|
519
|
+
const regId = await this.store.getLocalRegistrationId();
|
|
520
|
+
console.error(` - identityKey: ${identityKey ? "存在" : "缺失"}`);
|
|
521
|
+
console.error(` - registrationId: ${regId}`);
|
|
522
|
+
if (retryCount >= maxRetries) {
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
console.log(`[Session] 尝试重新建立会话...`);
|
|
526
|
+
try {
|
|
527
|
+
if (existingSession) {
|
|
528
|
+
console.log(`[Session] 删除旧会话`);
|
|
529
|
+
await this.store.removeSession(sessionKey);
|
|
530
|
+
existingSession = undefined;
|
|
531
|
+
}
|
|
532
|
+
if (msgType === MessageType.PREKEY_MESSAGE) {
|
|
533
|
+
console.log(`[Session] 收到的是 PREKEY_MESSAGE,等待发送方的 PreKey 即可重建会话`);
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
console.log(`[Session] 收到的是 WHISPER_MESSAGE,需要让对方重新发送 PREKEY_MESSAGE`);
|
|
537
|
+
}
|
|
538
|
+
retryCount++;
|
|
539
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
540
|
+
}
|
|
541
|
+
catch (rebuildError) {
|
|
542
|
+
console.error(`[Session] 重新建立会话失败:`, rebuildError);
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
377
545
|
}
|
|
378
|
-
console.error(` 上下文:`);
|
|
379
|
-
console.error(` - myAddress: ${myAddress}`);
|
|
380
|
-
console.error(` - peerAddress: ${peerAddress}`);
|
|
381
|
-
console.error(
|
|
382
|
-
` - msgType: ${msgType} (${msgType === MessageType.PREKEY_MESSAGE ? "PREKEY" : "WHISPER"})`,
|
|
383
|
-
);
|
|
384
|
-
console.error(` - sessionKey: ${sessionKey}`);
|
|
385
|
-
console.error(
|
|
386
|
-
` - existingSession: ${existingSession ? "存在" : "不存在"}`,
|
|
387
|
-
);
|
|
388
|
-
console.error(` - ciphertext.length: ${ciphertext.byteLength}`);
|
|
389
|
-
*/
|
|
390
|
-
console.log("error:", error);
|
|
391
|
-
throw error;
|
|
392
546
|
}
|
|
393
|
-
|
|
547
|
+
throw new MessengerError(MessengerErrorCode.DECRYPTION_FAILED, "Decryption failed after multiple attempts");
|
|
394
548
|
}
|
|
395
549
|
/**
|
|
396
550
|
* 获取本地身份公钥
|
|
@@ -407,4 +561,160 @@ export class MessengerSession {
|
|
|
407
561
|
return identity.registrationId;
|
|
408
562
|
}
|
|
409
563
|
}
|
|
564
|
+
export class SessionStateManager {
|
|
565
|
+
store;
|
|
566
|
+
constructor(store) {
|
|
567
|
+
this.store = store;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* 保存会话快照
|
|
571
|
+
*/
|
|
572
|
+
async snapshotSession(sessionKey) {
|
|
573
|
+
const sessionData = await this.store.loadSession(sessionKey);
|
|
574
|
+
return {
|
|
575
|
+
sessionData: sessionData || null,
|
|
576
|
+
timestamp: Date.now(),
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* 回滚会话状态
|
|
581
|
+
*/
|
|
582
|
+
async rollbackSession(sessionKey, snapshot) {
|
|
583
|
+
if (snapshot.sessionData) {
|
|
584
|
+
await this.store.storeSession(sessionKey, snapshot.sessionData);
|
|
585
|
+
console.log(`[SessionState] 回滚会话: ${sessionKey}`);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
await this.store.removeSession(sessionKey);
|
|
589
|
+
console.log(`[SessionState] 清除会话: ${sessionKey}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* 提交会话状态(保留新会话)
|
|
594
|
+
*/
|
|
595
|
+
async commitSession(_sessionKey) {
|
|
596
|
+
// 新会话已经在 decrypt 过程中保存到存储
|
|
597
|
+
// 这里可以添加额外的确认逻辑
|
|
598
|
+
console.log(`[SessionState] 提交新会话`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
export class DecryptionEngine {
|
|
602
|
+
sessionManager;
|
|
603
|
+
store;
|
|
604
|
+
constructor(store) {
|
|
605
|
+
this.store = store;
|
|
606
|
+
this.sessionManager = new SessionStateManager(store);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* 统一解密入口
|
|
610
|
+
*/
|
|
611
|
+
async decryptMessage(myAddress, peerAddress, ciphertext, msgType, peerDeviceId = DEFAULT_DEVICE_ID) {
|
|
612
|
+
const protocolAddress = new SignalProtocolAddress(peerAddress, peerDeviceId);
|
|
613
|
+
const sessionKey = protocolAddress.toString();
|
|
614
|
+
// 1. 保存会话快照(用于回滚)
|
|
615
|
+
const snapshot = await this.sessionManager.snapshotSession(sessionKey);
|
|
616
|
+
try {
|
|
617
|
+
if (msgType === MessageType.PREKEY_MESSAGE) {
|
|
618
|
+
return await this.handlePreKeyMessage(myAddress, peerAddress, ciphertext, protocolAddress, sessionKey, snapshot);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
return await this.handleWhisperMessage(myAddress, peerAddress, ciphertext, protocolAddress, sessionKey, snapshot);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
// 发生错误,回滚会话
|
|
626
|
+
await this.sessionManager.rollbackSession(sessionKey, snapshot);
|
|
627
|
+
return {
|
|
628
|
+
success: false,
|
|
629
|
+
sessionUpdated: false,
|
|
630
|
+
sessionRolledBack: true,
|
|
631
|
+
messageType: msgType === MessageType.PREKEY_MESSAGE
|
|
632
|
+
? "PREKEY"
|
|
633
|
+
: "WHISPER",
|
|
634
|
+
error: error instanceof Error ? error.message : String(error),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* 处理 PREKEY_MESSAGE
|
|
640
|
+
*/
|
|
641
|
+
async handlePreKeyMessage(myAddress, peerAddress, ciphertext, protocolAddress, sessionKey, snapshot) {
|
|
642
|
+
console.log(`[Decrypt] 处理 PREKEY_MESSAGE from ${peerAddress}`);
|
|
643
|
+
const cipher = new SessionCipher(this.store, protocolAddress);
|
|
644
|
+
const decoder = new TextDecoder();
|
|
645
|
+
// 尝试解密(这会可能建立新会话)
|
|
646
|
+
const ciphertextBinary = this.arrayBufferToBinary(ciphertext);
|
|
647
|
+
const plaintextBuffer = await cipher.decryptPreKeyWhisperMessage(ciphertextBinary, "binary");
|
|
648
|
+
const plaintext = decoder.decode(plaintextBuffer);
|
|
649
|
+
console.log(`[Decrypt] PREKEY_MESSAGE 解密成功`);
|
|
650
|
+
// 地址优先级判断
|
|
651
|
+
const myAddr = myAddress.toLowerCase();
|
|
652
|
+
const peerAddr = peerAddress.toLowerCase();
|
|
653
|
+
if (myAddr < peerAddr) {
|
|
654
|
+
// 我是小地址,我的会话优先
|
|
655
|
+
// 回滚到之前的会话状态
|
|
656
|
+
await this.sessionManager.rollbackSession(sessionKey, snapshot);
|
|
657
|
+
console.log(`[Decrypt] 小地址(${myAddr})优先,回滚会话,但消息已解密`);
|
|
658
|
+
return {
|
|
659
|
+
success: true,
|
|
660
|
+
plaintext,
|
|
661
|
+
sessionUpdated: false,
|
|
662
|
+
sessionRolledBack: true,
|
|
663
|
+
messageType: "PREKEY",
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// 我是大地址,接受对方的会话
|
|
668
|
+
await this.sessionManager.commitSession(sessionKey);
|
|
669
|
+
console.log(`[Decrypt] 大地址(${myAddr})接受新会话`);
|
|
670
|
+
return {
|
|
671
|
+
success: true,
|
|
672
|
+
plaintext,
|
|
673
|
+
sessionUpdated: true,
|
|
674
|
+
sessionRolledBack: false,
|
|
675
|
+
messageType: "PREKEY",
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* 处理 WHISPER_MESSAGE
|
|
681
|
+
*/
|
|
682
|
+
async handleWhisperMessage(_myAddress, peerAddress, ciphertext, protocolAddress, sessionKey, snapshot) {
|
|
683
|
+
console.log(`[Decrypt] 处理 WHISPER_MESSAGE from ${peerAddress}`);
|
|
684
|
+
// 检查是否有现有会话
|
|
685
|
+
const existingSession = await this.store.loadSession(sessionKey);
|
|
686
|
+
if (!existingSession) {
|
|
687
|
+
console.log(`[Decrypt] 无会话,WHISPER_MESSAGE 需要等待 PREKEY`);
|
|
688
|
+
await this.sessionManager.rollbackSession(sessionKey, snapshot);
|
|
689
|
+
return {
|
|
690
|
+
success: false,
|
|
691
|
+
sessionUpdated: false,
|
|
692
|
+
sessionRolledBack: false,
|
|
693
|
+
messageType: "WHISPER",
|
|
694
|
+
error: "NO_SESSION",
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
const cipher = new SessionCipher(this.store, protocolAddress);
|
|
698
|
+
const decoder = new TextDecoder();
|
|
699
|
+
// 尝试解密
|
|
700
|
+
const ciphertextBinary = this.arrayBufferToBinary(ciphertext);
|
|
701
|
+
const plaintextBuffer = await cipher.decryptWhisperMessage(ciphertextBinary, "binary");
|
|
702
|
+
console.log(`[Decrypt] WHISPER_MESSAGE 解密成功`);
|
|
703
|
+
return {
|
|
704
|
+
success: true,
|
|
705
|
+
plaintext: decoder.decode(plaintextBuffer),
|
|
706
|
+
sessionUpdated: true,
|
|
707
|
+
sessionRolledBack: false,
|
|
708
|
+
messageType: "WHISPER",
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
arrayBufferToBinary(buffer) {
|
|
712
|
+
const array = new Uint8Array(buffer);
|
|
713
|
+
let binary = "";
|
|
714
|
+
for (let i = 0; i < array.length; i++) {
|
|
715
|
+
binary += String.fromCharCode(array[i]);
|
|
716
|
+
}
|
|
717
|
+
return binary;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
410
720
|
//# sourceMappingURL=session.js.map
|