wowok 2.1.16 → 2.1.18
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 +2 -2
- package/dist/cjs/w/local/account.js +101 -28
- 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 +17 -8
- 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 +75 -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 +99 -150
- package/dist/cjs/w/messenger/messenger-manager.js.map +1 -1
- package/dist/cjs/w/messenger/messenger.d.ts +88 -31
- package/dist/cjs/w/messenger/messenger.js +448 -441
- 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 +13 -10
- package/dist/cjs/w/messenger/session.js +290 -176
- 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 +138 -5
- 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 +2 -2
- package/dist/esm/w/local/account.js +101 -28
- 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 +17 -8
- 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 +75 -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 +99 -150
- package/dist/esm/w/messenger/messenger-manager.js.map +1 -1
- package/dist/esm/w/messenger/messenger.d.ts +88 -31
- package/dist/esm/w/messenger/messenger.js +448 -441
- 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 +13 -10
- package/dist/esm/w/messenger/session.js +290 -176
- 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 +138 -5
- 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 +3 -3
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* 3. 消息加密和解密
|
|
11
11
|
*/
|
|
12
12
|
import { KeyHelper, SessionBuilder, SessionCipher, SignalProtocolAddress, } from "libsignal-protocol-typescript";
|
|
13
|
+
// eslint-disable-next-line import/no-cycle
|
|
13
14
|
import { Account } from "../local/account.js";
|
|
14
15
|
import { SignalProtocolStorage } from "./storage.js";
|
|
15
16
|
import { MessengerServerClient } from "./server.js";
|
|
@@ -62,8 +63,8 @@ export class MessengerSession {
|
|
|
62
63
|
prefixedPublicKey[0] = 0x05; // Signal Protocol 标准前缀
|
|
63
64
|
prefixedPublicKey.set(publicKey, 1);
|
|
64
65
|
const signalIdentity = {
|
|
65
|
-
privKey: privateKey.
|
|
66
|
-
pubKey: prefixedPublicKey.
|
|
66
|
+
privKey: privateKey.slice().buffer,
|
|
67
|
+
pubKey: prefixedPublicKey.slice().buffer,
|
|
67
68
|
};
|
|
68
69
|
await this.store.setIdentity(signalIdentity, registrationId);
|
|
69
70
|
const xed25519 = recoverXed25519FromX25519PrivateKey(privateKey);
|
|
@@ -89,17 +90,38 @@ export class MessengerSession {
|
|
|
89
90
|
*/
|
|
90
91
|
async registerDevice(userAddress) {
|
|
91
92
|
const identity = await this.ensureIdentity(userAddress);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
const signedPreKeyId = 1;
|
|
94
|
+
let signedPreKey;
|
|
95
|
+
let signedPreKeyPublicKey;
|
|
96
|
+
let signedPreKeySignature;
|
|
97
|
+
let isNewKey = false;
|
|
98
|
+
// 【关键修复】使用带签名的存储方法
|
|
99
|
+
const existingSignedPreKey = await this.store.loadSignedPreKeyWithSignature(signedPreKeyId);
|
|
100
|
+
if (existingSignedPreKey) {
|
|
101
|
+
signedPreKeyPublicKey = arrayBufferToUint8Array(existingSignedPreKey.pubKey);
|
|
102
|
+
signedPreKeySignature = arrayBufferToUint8Array(existingSignedPreKey.signature);
|
|
103
|
+
signedPreKey = {
|
|
104
|
+
keyId: signedPreKeyId,
|
|
105
|
+
keyPair: {
|
|
106
|
+
pubKey: existingSignedPreKey.pubKey,
|
|
107
|
+
privKey: existingSignedPreKey.privKey,
|
|
108
|
+
},
|
|
109
|
+
signature: existingSignedPreKey.signature,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
signedPreKey = await KeyHelper.generateSignedPreKey(identity.signalIdentity, signedPreKeyId);
|
|
114
|
+
signedPreKeyPublicKey = arrayBufferToUint8Array(signedPreKey.keyPair.pubKey);
|
|
115
|
+
signedPreKeySignature = arrayBufferToUint8Array(signedPreKey.signature);
|
|
116
|
+
isNewKey = true;
|
|
117
|
+
}
|
|
118
|
+
// 生成 One-Time PreKeys(暂不保存)
|
|
119
|
+
const oneTimePrekeys = await this.generatePreKeyBatch(this.config.prekey_count, userAddress);
|
|
96
120
|
const prefixedIdentityKey = arrayBufferToUint8Array(identity.signalIdentity.pubKey);
|
|
97
|
-
// 获取账户信息用于签名
|
|
98
121
|
const account = await Account.Instance().get(userAddress, false);
|
|
99
122
|
if (!account?.pubkey) {
|
|
100
123
|
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${userAddress}`);
|
|
101
124
|
}
|
|
102
|
-
// 构造签名
|
|
103
125
|
const timestamp = Date.now();
|
|
104
126
|
const nonce = this.generateNonce();
|
|
105
127
|
const message = `register:${account.pubkey}:${timestamp}:${nonce}`;
|
|
@@ -110,40 +132,111 @@ export class MessengerSession {
|
|
|
110
132
|
deviceId: DEFAULT_DEVICE_ID,
|
|
111
133
|
registrationId: identity.registrationId,
|
|
112
134
|
identityKey: bytesToBase64(prefixedIdentityKey),
|
|
113
|
-
|
|
135
|
+
signedPrekey: {
|
|
136
|
+
keyId: signedPreKeyId,
|
|
137
|
+
publicKey: bytesToBase64(signedPreKeyPublicKey),
|
|
138
|
+
signature: bytesToBase64(signedPreKeySignature),
|
|
139
|
+
},
|
|
140
|
+
prekeys: oneTimePrekeys,
|
|
114
141
|
publicKey: account.pubkey,
|
|
115
142
|
signatureScheme: "ED25519",
|
|
116
143
|
signature: bytesToBase64(signature),
|
|
117
144
|
timestamp,
|
|
118
145
|
nonce,
|
|
119
146
|
};
|
|
147
|
+
console.log(`[Session Debug] Sending register request to server...`);
|
|
120
148
|
await this.serverClient.registerDevice(registerRequest);
|
|
149
|
+
console.log(`[Session Debug] Server register request successful!`);
|
|
150
|
+
console.log(`[Session Debug] Verifying registration complete...`);
|
|
151
|
+
await this.verifyRegistrationComplete(userAddress);
|
|
152
|
+
console.log(`[Session Debug] Registration verification complete!`);
|
|
153
|
+
// 【关键修复】只有是新 key 时才保存到本地数据库(包含签名)
|
|
154
|
+
if (isNewKey) {
|
|
155
|
+
await this.store.storeSignedPreKeyWithSignature(signedPreKeyId, signedPreKey.keyPair, signedPreKeySignature.buffer.slice(0));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 验证注册是否完成
|
|
160
|
+
* 轮询检查服务器是否已存在该用户
|
|
161
|
+
*/
|
|
162
|
+
async verifyRegistrationComplete(userAddress, maxRetries = 10, retryDelay = 500) {
|
|
163
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
164
|
+
try {
|
|
165
|
+
const status = await this.serverClient.getPrekeyStatus(userAddress);
|
|
166
|
+
// 如果成功获取状态,说明注册已完成
|
|
167
|
+
console.log(`Registration verified for ${userAddress}, prekeys: ${status.currentCount}/${status.maxAllowed}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
// 如果是 404,说明注册还没完成,继续等待
|
|
172
|
+
if (error?.message?.includes("404") || error?.status === 404) {
|
|
173
|
+
if (i < maxRetries - 1) {
|
|
174
|
+
console.log(`Waiting for registration to complete... (${i + 1}/${maxRetries})`);
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// 其他错误,抛出
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw new MessengerError(MessengerErrorCode.REGISTRATION_FAILED, `Registration verification failed after ${maxRetries} attempts`);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 检查设备是否已在服务器上注册
|
|
187
|
+
* 通过查询服务器上的预密钥状态来判断
|
|
188
|
+
*/
|
|
189
|
+
async isDeviceRegistered(userAddress) {
|
|
190
|
+
try {
|
|
191
|
+
// 检查本地是否有 signedPreKey(使用带签名的方法)
|
|
192
|
+
const signedPreKey = await this.store.loadSignedPreKeyWithSignature(1);
|
|
193
|
+
if (!signedPreKey) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
// 尝试从服务器获取预密钥状态
|
|
197
|
+
const _status = await this.serverClient.getPrekeyStatus(userAddress);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
// 如果是 404,说明设备未注册
|
|
202
|
+
if (error?.message?.includes("404") || error?.status === 404) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
121
207
|
}
|
|
122
208
|
/**
|
|
123
|
-
*
|
|
209
|
+
* 生成预密钥批次(One-Time PreKeys)
|
|
124
210
|
*/
|
|
125
|
-
async generatePreKeyBatch(count) {
|
|
211
|
+
async generatePreKeyBatch(count, userAddress) {
|
|
126
212
|
if (count <= 0)
|
|
127
213
|
return [];
|
|
128
|
-
const identity = await this.ensureIdentity();
|
|
129
214
|
const meta = await this.store.getMeta();
|
|
130
215
|
let nextPreKeyId = meta.nextPreKeyId || 1;
|
|
131
216
|
const prekeys = [];
|
|
132
217
|
for (let i = 0; i < count; i++) {
|
|
133
218
|
const keyId = nextPreKeyId + i;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
219
|
+
const existingPreKey = await this.store.loadPreKey(keyId);
|
|
220
|
+
if (existingPreKey) {
|
|
221
|
+
const pubKeyBytes = new Uint8Array(existingPreKey.pubKey);
|
|
222
|
+
const publicKeyBase64 = bytesToBase64(pubKeyBytes);
|
|
223
|
+
const signResult = await Account.Instance().signData(userAddress, pubKeyBytes);
|
|
224
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
225
|
+
prekeys.push({
|
|
226
|
+
keyId,
|
|
227
|
+
publicKey: publicKeyBase64,
|
|
228
|
+
signature: bytesToBase64(signature),
|
|
229
|
+
});
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const preKey = await KeyHelper.generatePreKey(keyId);
|
|
233
|
+
const pubKeyBytes = new Uint8Array(preKey.keyPair.pubKey);
|
|
234
|
+
const signResult = await Account.Instance().signData(userAddress, pubKeyBytes);
|
|
235
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
236
|
+
await this.store.storePreKey(keyId, preKey.keyPair);
|
|
144
237
|
prekeys.push({
|
|
145
238
|
keyId,
|
|
146
|
-
publicKey: bytesToBase64(
|
|
239
|
+
publicKey: bytesToBase64(pubKeyBytes),
|
|
147
240
|
signature: bytesToBase64(signature),
|
|
148
241
|
});
|
|
149
242
|
}
|
|
@@ -153,44 +246,30 @@ export class MessengerSession {
|
|
|
153
246
|
}
|
|
154
247
|
/**
|
|
155
248
|
* 确保服务器上有足够可用的预密钥
|
|
156
|
-
*
|
|
157
|
-
* 逻辑:查询服务器当前预密钥数量,按需生成并上传,补充到服务器最大容量
|
|
158
249
|
*/
|
|
159
250
|
async ensurePreKeys(userAddress, force = false) {
|
|
160
|
-
// 1. 查询服务器当前预密钥状态
|
|
161
251
|
const status = await this.serverClient.getPrekeyStatus(userAddress);
|
|
162
|
-
// 2. 如果服务器已满且非强制模式,直接返回
|
|
163
252
|
if (!force && status.currentCount >= status.maxAllowed) {
|
|
164
|
-
|
|
253
|
+
return;
|
|
165
254
|
}
|
|
166
|
-
// 3. 计算需要补充的数量
|
|
167
255
|
const needCount = status.maxAllowed - status.currentCount;
|
|
168
256
|
if (needCount <= 0) {
|
|
169
|
-
/*console.log(
|
|
170
|
-
`No need to refill prekeys, server has ${status.currentCount}/${status.maxAllowed}`,
|
|
171
|
-
);*/
|
|
172
257
|
return;
|
|
173
258
|
}
|
|
174
|
-
/*console.log(
|
|
175
|
-
`Refilling prekeys: server has ${status.currentCount}/${status.maxAllowed}, need ${needCount}`,
|
|
176
|
-
);*/
|
|
177
|
-
// 4. 确保身份已初始化
|
|
178
259
|
await this.ensureIdentity(userAddress);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
260
|
+
const prekeys = await this.generatePreKeyBatch(needCount, userAddress);
|
|
261
|
+
if (prekeys.length === 0) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
182
264
|
const account = await Account.Instance().get(userAddress, false);
|
|
183
265
|
if (!account?.pubkey) {
|
|
184
266
|
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${userAddress}`);
|
|
185
267
|
}
|
|
186
|
-
// 7. 构造上传请求(包含签名)
|
|
187
|
-
// 8. 生成签名
|
|
188
268
|
const timestamp = Date.now();
|
|
189
269
|
const nonce = this.generateNonce();
|
|
190
270
|
const message = `upload_prekeys:${account.pubkey}:${timestamp}:${nonce}`;
|
|
191
271
|
const signResult = await Account.Instance().signData(userAddress, message);
|
|
192
272
|
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
193
|
-
// 9. 上传预密钥(服务器会自动丢弃超过最大数量的部分)
|
|
194
273
|
await this.serverClient.uploadPreKeys({
|
|
195
274
|
userAddress,
|
|
196
275
|
prekeys,
|
|
@@ -212,185 +291,220 @@ export class MessengerSession {
|
|
|
212
291
|
/**
|
|
213
292
|
* 建立与对方的会话
|
|
214
293
|
*/
|
|
215
|
-
async establishSession(myAddress, peerAddress, peerDeviceId = DEFAULT_DEVICE_ID) {
|
|
294
|
+
async establishSession(myAddress, peerAddress, peerDeviceId = DEFAULT_DEVICE_ID, remoteBundle) {
|
|
216
295
|
const protocolAddress = new SignalProtocolAddress(peerAddress, peerDeviceId);
|
|
217
|
-
// 检查是否已有会话
|
|
218
296
|
const existingSession = await this.store.loadSession(protocolAddress.toString());
|
|
219
297
|
if (existingSession) {
|
|
220
298
|
return;
|
|
221
299
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
300
|
+
if (!remoteBundle) {
|
|
301
|
+
const account = await Account.Instance().get(myAddress, false);
|
|
302
|
+
if (!account?.pubkey) {
|
|
303
|
+
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${myAddress}`);
|
|
304
|
+
}
|
|
305
|
+
const timestamp = Date.now();
|
|
306
|
+
const nonce = this.generateNonce();
|
|
307
|
+
const publicKeyWithFlag = account.pubkey;
|
|
308
|
+
const message = `get_bundle:${myAddress}:${timestamp}:${nonce}`;
|
|
309
|
+
const signResult = await Account.Instance().signData(myAddress, message);
|
|
310
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
311
|
+
remoteBundle = await this.serverClient.fetchRemoteBundle(peerAddress, myAddress, publicKeyWithFlag, {
|
|
312
|
+
signatureScheme: "ED25519",
|
|
313
|
+
signature: bytesToBase64(new Uint8Array(signature)),
|
|
314
|
+
timestamp,
|
|
315
|
+
nonce,
|
|
316
|
+
}, peerDeviceId);
|
|
226
317
|
}
|
|
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) {
|
|
318
|
+
const oneTimePreKey = remoteBundle.oneTimePrekey;
|
|
319
|
+
const signedPreKey = remoteBundle.signedPrekey;
|
|
320
|
+
if (!signedPreKey) {
|
|
244
321
|
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `No signed prekey available for ${peerAddress}`);
|
|
245
322
|
}
|
|
246
|
-
// 转换为 Signal Protocol 设备格式
|
|
247
323
|
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
324
|
const originalIdentityKeyWithPrefix = new Uint8Array(identityKeyBytes);
|
|
253
|
-
const
|
|
325
|
+
const signedPreKeyPublicBytes = base64ToBytes(signedPreKey.publicKey);
|
|
326
|
+
const signedPreKeySignatureBytes = base64ToBytes(signedPreKey.signature);
|
|
327
|
+
const originalSignedPreKeyWithPrefix = new Uint8Array(signedPreKeyPublicBytes);
|
|
328
|
+
let oneTimePreKeyData = undefined;
|
|
329
|
+
if (oneTimePreKey) {
|
|
330
|
+
const oneTimePreKeyPublicBytes = base64ToBytes(oneTimePreKey.publicKey);
|
|
331
|
+
const originalOneTimePreKeyWithPrefix = new Uint8Array(oneTimePreKeyPublicBytes);
|
|
332
|
+
oneTimePreKeyData = {
|
|
333
|
+
keyId: oneTimePreKey.keyId,
|
|
334
|
+
publicKey: originalOneTimePreKeyWithPrefix.buffer.slice(originalOneTimePreKeyWithPrefix.byteOffset, originalOneTimePreKeyWithPrefix.byteOffset +
|
|
335
|
+
originalOneTimePreKeyWithPrefix.byteLength),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
254
338
|
const device = {
|
|
255
339
|
registrationId: remoteBundle.registrationId,
|
|
256
340
|
identityKey: originalIdentityKeyWithPrefix.buffer.slice(originalIdentityKeyWithPrefix.byteOffset, originalIdentityKeyWithPrefix.byteOffset +
|
|
257
341
|
originalIdentityKeyWithPrefix.byteLength),
|
|
258
342
|
signedPreKey: {
|
|
259
|
-
keyId:
|
|
260
|
-
publicKey:
|
|
261
|
-
|
|
262
|
-
signature: new Uint8Array(
|
|
343
|
+
keyId: signedPreKey.keyId,
|
|
344
|
+
publicKey: originalSignedPreKeyWithPrefix.buffer.slice(originalSignedPreKeyWithPrefix.byteOffset, originalSignedPreKeyWithPrefix.byteOffset +
|
|
345
|
+
originalSignedPreKeyWithPrefix.byteLength),
|
|
346
|
+
signature: new Uint8Array(signedPreKeySignatureBytes).buffer.slice(signedPreKeySignatureBytes.byteOffset, signedPreKeySignatureBytes.byteOffset +
|
|
347
|
+
signedPreKeySignatureBytes.byteLength),
|
|
263
348
|
},
|
|
264
|
-
|
|
265
|
-
// 这是可以的,Signal Protocol 支持仅使用 signedPreKey 建立会话
|
|
266
|
-
preKey: undefined,
|
|
349
|
+
preKey: oneTimePreKeyData,
|
|
267
350
|
};
|
|
268
|
-
// 建立会话
|
|
269
351
|
const builder = new SessionBuilder(this.store, protocolAddress);
|
|
270
352
|
await builder.processPreKey(device);
|
|
271
|
-
// 显式存储对方的身份密钥,以便后续解密对方的回复
|
|
272
|
-
// 使用 protocolAddress.toString() 作为键(包含 deviceId)
|
|
273
353
|
await this.store.saveIdentity(protocolAddress.toString(), device.identityKey);
|
|
274
354
|
}
|
|
275
355
|
/**
|
|
276
356
|
* 加密消息
|
|
277
|
-
* @param myAddress 发送方地址
|
|
278
|
-
* @param peerAddress 接收方地址
|
|
279
|
-
* @param plaintext 明文
|
|
280
|
-
* @param peerDeviceId 设备ID
|
|
281
|
-
* @returns 加密后的消息(type 表示底层 Signal Protocol 消息类型:3=PreKeyMessage, 1=WhisperMessage)
|
|
282
357
|
*/
|
|
283
358
|
async encryptMessage(myAddress, peerAddress, plaintext, peerDeviceId = DEFAULT_DEVICE_ID) {
|
|
284
359
|
const protocolAddress = new SignalProtocolAddress(peerAddress, peerDeviceId);
|
|
285
|
-
// 检查是否已有会话(用于确定底层消息类型)
|
|
286
360
|
const sessionKey = protocolAddress.toString();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
361
|
+
let existingSession = await this.store.loadSession(sessionKey);
|
|
362
|
+
let isNewSession = !existingSession;
|
|
363
|
+
let retryCount = 0;
|
|
364
|
+
const maxRetries = 2;
|
|
365
|
+
while (retryCount <= maxRetries) {
|
|
366
|
+
try {
|
|
367
|
+
if (isNewSession) {
|
|
368
|
+
console.log(`[Session] 建立新会话 (尝试 ${retryCount + 1}/${maxRetries + 1})`);
|
|
369
|
+
const account = await Account.Instance().get(myAddress, false);
|
|
370
|
+
if (!account?.pubkey) {
|
|
371
|
+
throw new MessengerError(MessengerErrorCode.IDENTITY_NOT_FOUND, `Account not found for ${myAddress}`);
|
|
372
|
+
}
|
|
373
|
+
const timestamp = Date.now();
|
|
374
|
+
const nonce = this.generateNonce();
|
|
375
|
+
const publicKeyWithFlag = account.pubkey;
|
|
376
|
+
const message = `get_bundle:${myAddress}:${timestamp}:${nonce}`;
|
|
377
|
+
const signResult = await Account.Instance().signData(myAddress, message);
|
|
378
|
+
const signature = Buffer.from(signResult.signature.slice(2), "hex");
|
|
379
|
+
const remoteBundle = await this.serverClient.fetchRemoteBundle(peerAddress, myAddress, publicKeyWithFlag, {
|
|
380
|
+
signatureScheme: "ED25519",
|
|
381
|
+
signature: bytesToBase64(new Uint8Array(signature)),
|
|
382
|
+
timestamp,
|
|
383
|
+
nonce,
|
|
384
|
+
}, peerDeviceId);
|
|
385
|
+
await this.establishSession(myAddress, peerAddress, peerDeviceId, remoteBundle);
|
|
386
|
+
}
|
|
387
|
+
const cipher = new SessionCipher(this.store, protocolAddress);
|
|
388
|
+
const encoder = new TextEncoder();
|
|
389
|
+
const cipherMessage = await cipher.encrypt(encoder.encode(plaintext).buffer);
|
|
390
|
+
let bodyBuffer;
|
|
391
|
+
if (typeof cipherMessage.body === "string") {
|
|
392
|
+
const bodyBytes = Buffer.from(cipherMessage.body, "binary");
|
|
393
|
+
bodyBuffer = new Uint8Array(bodyBytes).buffer;
|
|
394
|
+
}
|
|
395
|
+
else if (cipherMessage.body) {
|
|
396
|
+
bodyBuffer = cipherMessage.body;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
throw new MessengerError(MessengerErrorCode.ENCRYPTION_FAILED, "Cipher message body is empty");
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
type: cipherMessage.type,
|
|
403
|
+
body: bodyBuffer,
|
|
404
|
+
registrationId: cipherMessage.registrationId,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
console.error(`[Session] 加密失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`, error);
|
|
409
|
+
if (retryCount >= maxRetries) {
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
console.log(`[Session] 尝试删除旧会话并重新建立...`);
|
|
413
|
+
try {
|
|
414
|
+
if (existingSession) {
|
|
415
|
+
await this.store.removeSession(sessionKey);
|
|
416
|
+
existingSession = undefined;
|
|
417
|
+
}
|
|
418
|
+
isNewSession = true;
|
|
419
|
+
retryCount++;
|
|
420
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
421
|
+
}
|
|
422
|
+
catch (rebuildError) {
|
|
423
|
+
console.error(`[Session] 重新建立会话失败:`, rebuildError);
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
307
427
|
}
|
|
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
|
-
};
|
|
428
|
+
throw new MessengerError(MessengerErrorCode.ENCRYPTION_FAILED, "Encryption failed after multiple attempts");
|
|
319
429
|
}
|
|
320
430
|
/**
|
|
321
431
|
* 解密消息
|
|
322
432
|
*/
|
|
323
433
|
async decryptMessage(myAddress, peerAddress, ciphertext, msgType, peerDeviceId = DEFAULT_DEVICE_ID) {
|
|
324
434
|
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
435
|
const sessionKey = protocolAddress.toString();
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const ciphertextArray = new Uint8Array(ciphertext);
|
|
436
|
+
let existingSession = await this.store.loadSession(sessionKey);
|
|
437
|
+
const decoder = new TextDecoder();
|
|
335
438
|
let ciphertextBinary = "";
|
|
439
|
+
const ciphertextArray = new Uint8Array(ciphertext);
|
|
336
440
|
for (let i = 0; i < ciphertextArray.length; i++) {
|
|
337
441
|
ciphertextBinary += String.fromCharCode(ciphertextArray[i]);
|
|
338
442
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
443
|
+
let retryCount = 0;
|
|
444
|
+
const maxRetries = 2;
|
|
445
|
+
while (retryCount <= maxRetries) {
|
|
446
|
+
try {
|
|
447
|
+
const cipher = new SessionCipher(this.store, protocolAddress);
|
|
448
|
+
let plaintextBuffer;
|
|
449
|
+
if (msgType === MessageType.PREKEY_MESSAGE) {
|
|
450
|
+
console.log(`[Session] 收到 PREKEY_MESSAGE,发送方想用新的 PreKey Bundle 建立会话`);
|
|
451
|
+
if (existingSession && retryCount === 0) {
|
|
452
|
+
console.log(`[Session] 检测到已有会话,但收到 PREKEY_MESSAGE,先删除旧会话以建立新会话`);
|
|
453
|
+
await this.store.removeSession(sessionKey);
|
|
454
|
+
existingSession = undefined;
|
|
455
|
+
}
|
|
456
|
+
plaintextBuffer = await cipher.decryptPreKeyWhisperMessage(ciphertextBinary, "binary");
|
|
457
|
+
console.log(`[Session] PREKEY_MESSAGE 解密成功,新会话已建立`);
|
|
348
458
|
}
|
|
349
459
|
else {
|
|
350
|
-
plaintextBuffer = await cipher.
|
|
460
|
+
plaintextBuffer = await cipher.decryptWhisperMessage(ciphertextBinary, "binary");
|
|
351
461
|
}
|
|
462
|
+
return decoder.decode(plaintextBuffer);
|
|
352
463
|
}
|
|
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
|
-
|
|
464
|
+
catch (error) {
|
|
465
|
+
console.error(`[Session Debug] 解密失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`);
|
|
466
|
+
console.error(` 错误类型: ${error instanceof Error ? error.constructor.name : typeof error}`);
|
|
467
|
+
console.error(` 错误消息: ${error instanceof Error ? error.message : String(error)}`);
|
|
468
|
+
if (error instanceof Error && error.stack) {
|
|
469
|
+
console.error(` 堆栈: ${error.stack.split("\n")[0]}`);
|
|
470
|
+
}
|
|
471
|
+
console.error(` 上下文:`);
|
|
472
|
+
console.error(` - myAddress: ${myAddress}`);
|
|
473
|
+
console.error(` - peerAddress: ${peerAddress}`);
|
|
474
|
+
console.error(` - msgType: ${msgType} (${msgType === MessageType.PREKEY_MESSAGE ? "PREKEY" : "WHISPER"})`);
|
|
475
|
+
console.error(` - sessionKey: ${sessionKey}`);
|
|
476
|
+
console.error(` - existingSession: ${existingSession ? "存在" : "不存在"}`);
|
|
477
|
+
console.error(` - ciphertext.length: ${ciphertext.byteLength}`);
|
|
478
|
+
const identityKey = await this.store.getIdentityKeyPair();
|
|
479
|
+
const regId = await this.store.getLocalRegistrationId();
|
|
480
|
+
console.error(` - identityKey: ${identityKey ? "存在" : "缺失"}`);
|
|
481
|
+
console.error(` - registrationId: ${regId}`);
|
|
482
|
+
if (retryCount >= maxRetries) {
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
console.log(`[Session] 尝试重新建立会话...`);
|
|
486
|
+
try {
|
|
487
|
+
if (existingSession) {
|
|
488
|
+
console.log(`[Session] 删除旧会话`);
|
|
489
|
+
await this.store.removeSession(sessionKey);
|
|
490
|
+
existingSession = undefined;
|
|
491
|
+
}
|
|
492
|
+
if (msgType === MessageType.PREKEY_MESSAGE) {
|
|
493
|
+
console.log(`[Session] 收到的是 PREKEY_MESSAGE,等待发送方的 PreKey 即可重建会话`);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
console.log(`[Session] 收到的是 WHISPER_MESSAGE,需要让对方重新发送 PREKEY_MESSAGE`);
|
|
497
|
+
}
|
|
498
|
+
retryCount++;
|
|
499
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
500
|
+
}
|
|
501
|
+
catch (rebuildError) {
|
|
502
|
+
console.error(`[Session] 重新建立会话失败:`, rebuildError);
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
377
505
|
}
|
|
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
506
|
}
|
|
393
|
-
|
|
507
|
+
throw new MessengerError(MessengerErrorCode.DECRYPTION_FAILED, "Decryption failed after multiple attempts");
|
|
394
508
|
}
|
|
395
509
|
/**
|
|
396
510
|
* 获取本地身份公钥
|