chore: 完成v6.7.0版本迭代更新
本次更新涵盖多个功能模块的优化与新增: 1. 新增Wi-Fi直连配对方式与协作画布模块 2. 完成设备管理重命名API与前端适配 3. 优化日签卡片空数据保护与UI细节 4. 新增剪贴板工具与每日运势会话 5. 修复应用锁恢复、语音消息录制等已知问题 6. 完善文件传输统计与配对逻辑 7. 更新安卓权限配置与iOS隐私描述 8. 新增自动化测试脚本与文档 9. 清理旧版审计报告与测试文件
This commit is contained in:
374
server/index.js
374
server/index.js
@@ -26,8 +26,10 @@ class SnapdropServer {
|
||||
|
||||
this._rooms = {};
|
||||
this._userIdIndex = {};
|
||||
this._fingerprintIndex = {};
|
||||
this._pairingRecords = {};
|
||||
this._pendingPairRequests = {};
|
||||
this._canvasRooms = {};
|
||||
this._loadPairingRecords();
|
||||
|
||||
console.log('Snapdrop is running on port', port);
|
||||
@@ -117,26 +119,42 @@ class SnapdropServer {
|
||||
case 'heartbeat':
|
||||
sender.lastBeat = Date.now();
|
||||
break;
|
||||
case 'textMessage':
|
||||
this._handleTextMessage(sender, message);
|
||||
break;
|
||||
case 'fileMeta':
|
||||
this._handleFileMeta(sender, message);
|
||||
break;
|
||||
case 'canvas-stroke':
|
||||
this._handleCanvasStroke(sender, message);
|
||||
break;
|
||||
case 'canvas-cursor':
|
||||
this._handleCanvasCursor(sender, message);
|
||||
break;
|
||||
case 'canvas-join':
|
||||
this._handleCanvasJoin(sender, message);
|
||||
break;
|
||||
case 'canvas-leave':
|
||||
this._handleCanvasLeave(sender, message);
|
||||
break;
|
||||
case 'canvas-snapshot':
|
||||
this._handleCanvasSnapshot(sender, message);
|
||||
break;
|
||||
}
|
||||
|
||||
const handledTypes = ['disconnect', 'pong', 'register', 'discoverMyDevices', 'transportNegotiate', 'wsRelay', 'pair-request', 'pairRequest', 'pair-accept', 'pairAccept', 'pair-reject', 'pairReject', 'heartbeat'];
|
||||
const handledTypes = ['disconnect', 'pong', 'register', 'discoverMyDevices', 'transportNegotiate', 'wsRelay', 'pair-request', 'pairRequest', 'pair-accept', 'pairAccept', 'pair-reject', 'pairReject', 'heartbeat', 'textMessage', 'fileMeta', 'canvas-stroke', 'canvas-cursor', 'canvas-join', 'canvas-leave', 'canvas-snapshot'];
|
||||
if (message.to && !handledTypes.includes(message.type)) {
|
||||
const recipientId = message.to;
|
||||
let recipient = null;
|
||||
if (this._rooms[sender.ip] && this._rooms[sender.ip][recipientId]) {
|
||||
recipient = this._rooms[sender.ip][recipientId];
|
||||
} else {
|
||||
for (const roomId in this._rooms) {
|
||||
if (this._rooms[roomId][recipientId]) {
|
||||
recipient = this._rooms[roomId][recipientId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let recipientId = message.to;
|
||||
let recipient = this._resolveTarget(sender, recipientId);
|
||||
|
||||
if (recipient) {
|
||||
delete message.to;
|
||||
message.sender = sender.id;
|
||||
message.from = sender.fingerprint || sender.id;
|
||||
message.fromPeerId = sender.id;
|
||||
this._send(recipient, message);
|
||||
} else {
|
||||
console.log('Message routing failed: target not found for', recipientId, 'from', sender.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -158,6 +176,23 @@ class SnapdropServer {
|
||||
}
|
||||
this._userIdIndex[sender.userId].add(sender.id);
|
||||
}
|
||||
if (data.fingerprint) {
|
||||
const oldFingerprint = sender.fingerprint;
|
||||
sender.fingerprint = data.fingerprint;
|
||||
if (oldFingerprint && this._fingerprintIndex[oldFingerprint] === sender.id) {
|
||||
delete this._fingerprintIndex[oldFingerprint];
|
||||
}
|
||||
const existingPeerId = this._fingerprintIndex[sender.fingerprint];
|
||||
if (existingPeerId && existingPeerId !== sender.id) {
|
||||
const existingPeer = this._findPeer(existingPeerId);
|
||||
if (existingPeer) {
|
||||
console.log('Replacing old connection for fingerprint:', sender.fingerprint, 'old peer:', existingPeerId, 'new peer:', sender.id);
|
||||
this._leaveRoom(existingPeer);
|
||||
}
|
||||
}
|
||||
this._fingerprintIndex[sender.fingerprint] = sender.id;
|
||||
console.log('Register fingerprint mapping:', sender.fingerprint, '->', sender.id);
|
||||
}
|
||||
if (data.ipCity) sender.ipCity = data.ipCity;
|
||||
if (data.ipRange) sender.ipRange = data.ipRange;
|
||||
if (data.deviceModel) sender.deviceModel = data.deviceModel;
|
||||
@@ -168,6 +203,7 @@ class SnapdropServer {
|
||||
this._send(sender, {
|
||||
type: 'registered',
|
||||
id: sender.id,
|
||||
fingerprint: sender.fingerprint || '',
|
||||
userId: sender.userId
|
||||
});
|
||||
}
|
||||
@@ -184,30 +220,34 @@ class SnapdropServer {
|
||||
return;
|
||||
}
|
||||
|
||||
const myDevices = [];
|
||||
const seenFingerprints = new Map();
|
||||
for (const roomId in this._rooms) {
|
||||
for (const peerId in this._rooms[roomId]) {
|
||||
const peer = this._rooms[roomId][peerId];
|
||||
if (peer.id === sender.id) continue;
|
||||
if (peer.userId === userId) {
|
||||
myDevices.push({
|
||||
id: peer.id,
|
||||
alias: peer.name.displayName,
|
||||
deviceName: peer.name.deviceName,
|
||||
deviceModel: peer.deviceModel || '',
|
||||
platform: peer.platform || '',
|
||||
ip: peer.ip,
|
||||
ipCity: peer.ipCity || '',
|
||||
isOnline: true,
|
||||
rtcSupported: peer.rtcSupported
|
||||
});
|
||||
const fp = peer.fingerprint || peer.id;
|
||||
if (!seenFingerprints.has(fp)) {
|
||||
seenFingerprints.set(fp, {
|
||||
id: peer.id,
|
||||
fingerprint: peer.fingerprint || '',
|
||||
alias: peer.name.displayName,
|
||||
deviceName: peer.name.deviceName,
|
||||
deviceModel: peer.deviceModel || '',
|
||||
platform: peer.platform || '',
|
||||
ip: peer.ip,
|
||||
ipCity: peer.ipCity || '',
|
||||
isOnline: true,
|
||||
rtcSupported: peer.rtcSupported
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._send(sender, {
|
||||
type: 'myDevicesResponse',
|
||||
devices: myDevices
|
||||
devices: Array.from(seenFingerprints.values())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,12 +255,10 @@ class SnapdropServer {
|
||||
const targetId = message.to;
|
||||
if (!targetId) return;
|
||||
|
||||
let targetPeer = null;
|
||||
for (const roomId in this._rooms) {
|
||||
if (this._rooms[roomId][targetId]) {
|
||||
targetPeer = this._rooms[roomId][targetId];
|
||||
break;
|
||||
}
|
||||
let targetPeer = this._findPeer(targetId);
|
||||
|
||||
if (!targetPeer && this._fingerprintIndex[targetId]) {
|
||||
targetPeer = this._findPeer(this._fingerprintIndex[targetId]);
|
||||
}
|
||||
|
||||
if (!targetPeer) {
|
||||
@@ -234,7 +272,7 @@ class SnapdropServer {
|
||||
|
||||
this._send(targetPeer, {
|
||||
type: 'transportNegotiate',
|
||||
from: sender.id,
|
||||
from: sender.fingerprint || sender.id,
|
||||
transport: message.transport,
|
||||
payload: message.payload || {}
|
||||
});
|
||||
@@ -245,6 +283,11 @@ class SnapdropServer {
|
||||
if (!targetId) return;
|
||||
|
||||
let targetPeer = this._findPeer(targetId);
|
||||
|
||||
if (!targetPeer && this._fingerprintIndex[targetId]) {
|
||||
targetPeer = this._findPeer(this._fingerprintIndex[targetId]);
|
||||
}
|
||||
|
||||
if (!targetPeer) {
|
||||
this._send(sender, {
|
||||
type: 'pair-response',
|
||||
@@ -267,7 +310,7 @@ class SnapdropServer {
|
||||
|
||||
this._send(targetPeer, {
|
||||
type: 'pair-request',
|
||||
from: sender.id,
|
||||
from: sender.fingerprint || sender.id,
|
||||
requestId: requestId,
|
||||
fingerprint: this._pendingPairRequests[requestId].fingerprint,
|
||||
payload: message.payload || {}
|
||||
@@ -310,7 +353,7 @@ class SnapdropServer {
|
||||
if (fromPeer) {
|
||||
this._send(fromPeer, {
|
||||
type: 'pair-response',
|
||||
from: sender.id,
|
||||
from: sender.fingerprint || sender.id,
|
||||
success: true,
|
||||
requestId: requestId
|
||||
});
|
||||
@@ -318,7 +361,7 @@ class SnapdropServer {
|
||||
|
||||
this._send(sender, {
|
||||
type: 'pair-response',
|
||||
from: req.fromId,
|
||||
from: fromPeer ? (fromPeer.fingerprint || fromPeer.id) : req.fromId,
|
||||
success: true,
|
||||
requestId: requestId
|
||||
});
|
||||
@@ -335,7 +378,7 @@ class SnapdropServer {
|
||||
if (fromPeer) {
|
||||
this._send(fromPeer, {
|
||||
type: 'pair-response',
|
||||
from: sender.id,
|
||||
from: sender.fingerprint || sender.id,
|
||||
success: false,
|
||||
requestId: requestId,
|
||||
error: 'Rejected'
|
||||
@@ -352,6 +395,81 @@ class SnapdropServer {
|
||||
return null;
|
||||
}
|
||||
|
||||
_resolveTarget(sender, targetId) {
|
||||
if (this._rooms[sender.ip] && this._rooms[sender.ip][targetId]) {
|
||||
return this._rooms[sender.ip][targetId];
|
||||
}
|
||||
|
||||
let recipient = this._findPeer(targetId);
|
||||
if (recipient) return recipient;
|
||||
|
||||
if (this._fingerprintIndex[targetId]) {
|
||||
const mappedPeerId = this._fingerprintIndex[targetId];
|
||||
console.log('Fingerprint mapping:', targetId, '->', mappedPeerId);
|
||||
recipient = this._findPeer(mappedPeerId);
|
||||
if (recipient) return recipient;
|
||||
}
|
||||
|
||||
if (sender.userId) {
|
||||
const sameUserPeerIds = this._userIdIndex[sender.userId];
|
||||
if (sameUserPeerIds) {
|
||||
for (const peerId of sameUserPeerIds) {
|
||||
if (peerId !== sender.id) {
|
||||
recipient = this._findPeer(peerId);
|
||||
if (recipient) return recipient;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._userIdIndex[targetId]) {
|
||||
for (const peerId of this._userIdIndex[targetId]) {
|
||||
recipient = this._findPeer(peerId);
|
||||
if (recipient) return recipient;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_handleTextMessage(sender, message) {
|
||||
const targetId = message.to;
|
||||
if (!targetId) return;
|
||||
|
||||
const recipient = this._resolveTarget(sender, targetId);
|
||||
if (recipient) {
|
||||
this._send(recipient, {
|
||||
type: 'textMessage',
|
||||
from: sender.fingerprint || sender.id,
|
||||
fromPeerId: sender.id,
|
||||
to: recipient.id,
|
||||
message: message.message || message.payload || {},
|
||||
timestamp: message.timestamp || Date.now()
|
||||
});
|
||||
} else {
|
||||
console.log('textMessage routing failed: target not found for', targetId, 'from', sender.id);
|
||||
}
|
||||
}
|
||||
|
||||
_handleFileMeta(sender, message) {
|
||||
const targetId = message.to;
|
||||
if (!targetId) return;
|
||||
|
||||
const recipient = this._resolveTarget(sender, targetId);
|
||||
if (recipient) {
|
||||
this._send(recipient, {
|
||||
type: 'fileMeta',
|
||||
from: sender.fingerprint || sender.id,
|
||||
fromPeerId: sender.id,
|
||||
to: recipient.id,
|
||||
file: message.file || message.payload || {},
|
||||
timestamp: message.timestamp || Date.now()
|
||||
});
|
||||
} else {
|
||||
console.log('fileMeta routing failed: target not found for', targetId, 'from', sender.id);
|
||||
}
|
||||
}
|
||||
|
||||
_handleWsRelay(sender, message) {
|
||||
const targetId = message.to;
|
||||
if (!targetId) return;
|
||||
@@ -364,10 +482,37 @@ class SnapdropServer {
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetPeer && this._fingerprintIndex[targetId]) {
|
||||
const mappedPeerId = this._fingerprintIndex[targetId];
|
||||
for (const roomId in this._rooms) {
|
||||
if (this._rooms[roomId][mappedPeerId]) {
|
||||
targetPeer = this._rooms[roomId][mappedPeerId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetPeer && sender.userId) {
|
||||
const sameUserPeerIds = this._userIdIndex[sender.userId];
|
||||
if (sameUserPeerIds) {
|
||||
for (const peerId of sameUserPeerIds) {
|
||||
if (peerId !== sender.id) {
|
||||
for (const roomId in this._rooms) {
|
||||
if (this._rooms[roomId][peerId]) {
|
||||
targetPeer = this._rooms[roomId][peerId];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetPeer) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetPeer) {
|
||||
this._send(sender, {
|
||||
type: 'wsRelay',
|
||||
from: sender.id,
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: {
|
||||
relayType: 'error',
|
||||
data: { error: 'Target peer not found', targetId: targetId }
|
||||
@@ -384,11 +529,138 @@ class SnapdropServer {
|
||||
|
||||
this._send(targetPeer, {
|
||||
type: 'wsRelay',
|
||||
from: sender.id,
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: payload
|
||||
});
|
||||
}
|
||||
|
||||
_handleCanvasJoin(sender, message) {
|
||||
const payload = message.payload || {};
|
||||
const canvasId = payload.canvasId;
|
||||
if (!canvasId) return;
|
||||
|
||||
if (!this._canvasRooms[canvasId]) {
|
||||
this._canvasRooms[canvasId] = new Set();
|
||||
}
|
||||
this._canvasRooms[canvasId].add(sender.id);
|
||||
sender.canvasRoomId = canvasId;
|
||||
|
||||
const members = Array.from(this._canvasRooms[canvasId]);
|
||||
for (const peerId of this._canvasRooms[canvasId]) {
|
||||
if (peerId === sender.id) continue;
|
||||
const peer = this._findPeer(peerId);
|
||||
if (peer) {
|
||||
this._send(peer, {
|
||||
type: 'canvas-join',
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: { canvasId: canvasId, members: members }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._send(sender, {
|
||||
type: 'canvas-join',
|
||||
from: 'server',
|
||||
payload: { canvasId: canvasId, members: members }
|
||||
});
|
||||
console.log('Canvas join:', sender.id, 'joined canvas', canvasId, 'members:', members.length);
|
||||
}
|
||||
|
||||
_handleCanvasLeave(sender, message) {
|
||||
const payload = message.payload || {};
|
||||
const canvasId = payload.canvasId || sender.canvasRoomId;
|
||||
if (!canvasId || !this._canvasRooms[canvasId]) return;
|
||||
|
||||
this._canvasRooms[canvasId].delete(sender.id);
|
||||
sender.canvasRoomId = null;
|
||||
|
||||
for (const peerId of this._canvasRooms[canvasId]) {
|
||||
const peer = this._findPeer(peerId);
|
||||
if (peer) {
|
||||
this._send(peer, {
|
||||
type: 'canvas-leave',
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: { canvasId: canvasId }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this._canvasRooms[canvasId].size === 0) {
|
||||
delete this._canvasRooms[canvasId];
|
||||
}
|
||||
console.log('Canvas leave:', sender.id, 'left canvas', canvasId);
|
||||
}
|
||||
|
||||
_handleCanvasStroke(sender, message) {
|
||||
const payload = message.payload || {};
|
||||
const canvasId = payload.canvasId || sender.canvasRoomId;
|
||||
if (!canvasId || !this._canvasRooms[canvasId]) return;
|
||||
|
||||
for (const peerId of this._canvasRooms[canvasId]) {
|
||||
if (peerId === sender.id) continue;
|
||||
const peer = this._findPeer(peerId);
|
||||
if (peer) {
|
||||
this._send(peer, {
|
||||
type: 'canvas-stroke',
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: payload
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleCanvasCursor(sender, message) {
|
||||
const payload = message.payload || {};
|
||||
const canvasId = payload.canvasId || sender.canvasRoomId;
|
||||
if (!canvasId || !this._canvasRooms[canvasId]) return;
|
||||
|
||||
for (const peerId of this._canvasRooms[canvasId]) {
|
||||
if (peerId === sender.id) continue;
|
||||
const peer = this._findPeer(peerId);
|
||||
if (peer) {
|
||||
this._send(peer, {
|
||||
type: 'canvas-cursor',
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: payload
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleCanvasSnapshot(sender, message) {
|
||||
const payload = message.payload || {};
|
||||
const canvasId = payload.canvasId || sender.canvasRoomId;
|
||||
const action = payload.action || 'request';
|
||||
if (!canvasId || !this._canvasRooms[canvasId]) return;
|
||||
|
||||
if (action === 'request') {
|
||||
const members = Array.from(this._canvasRooms[canvasId]);
|
||||
const otherMember = members.find(id => id !== sender.id);
|
||||
if (otherMember) {
|
||||
const peer = this._findPeer(otherMember);
|
||||
if (peer) {
|
||||
this._send(peer, {
|
||||
type: 'canvas-snapshot',
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: { canvasId: canvasId, action: 'request' }
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (action === 'response') {
|
||||
const targetId = message.to;
|
||||
if (targetId) {
|
||||
const targetPeer = this._findPeer(targetId);
|
||||
if (targetPeer) {
|
||||
this._send(targetPeer, {
|
||||
type: 'canvas-snapshot',
|
||||
from: sender.fingerprint || sender.id,
|
||||
payload: payload
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_joinRoom(peer) {
|
||||
if (!this._rooms[peer.ip]) {
|
||||
this._rooms[peer.ip] = {};
|
||||
@@ -426,6 +698,28 @@ class SnapdropServer {
|
||||
if (!this._rooms[peer.ip] || !this._rooms[peer.ip][peer.id]) return;
|
||||
this._cancelKeepAlive(this._rooms[peer.ip][peer.id]);
|
||||
|
||||
if (peer.canvasRoomId && this._canvasRooms[peer.canvasRoomId]) {
|
||||
this._canvasRooms[peer.canvasRoomId].delete(peer.id);
|
||||
for (const peerId of this._canvasRooms[peer.canvasRoomId]) {
|
||||
const otherPeer = this._findPeer(peerId);
|
||||
if (otherPeer) {
|
||||
this._send(otherPeer, {
|
||||
type: 'canvas-leave',
|
||||
from: peer.fingerprint || peer.id,
|
||||
payload: { canvasId: peer.canvasRoomId }
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this._canvasRooms[peer.canvasRoomId].size === 0) {
|
||||
delete this._canvasRooms[peer.canvasRoomId];
|
||||
}
|
||||
peer.canvasRoomId = null;
|
||||
}
|
||||
|
||||
if (peer.fingerprint && this._fingerprintIndex[peer.fingerprint] === peer.id) {
|
||||
delete this._fingerprintIndex[peer.fingerprint];
|
||||
}
|
||||
|
||||
delete this._rooms[peer.ip][peer.id];
|
||||
|
||||
if (peer.userId && this._userIdIndex[peer.userId]) {
|
||||
@@ -489,10 +783,12 @@ class Peer {
|
||||
this._setName(request);
|
||||
|
||||
this.userId = '';
|
||||
this.fingerprint = '';
|
||||
this.ipCity = '';
|
||||
this.ipRange = '';
|
||||
this.deviceModel = '';
|
||||
this.platform = '';
|
||||
this.canvasRoomId = null;
|
||||
|
||||
this.timerId = 0;
|
||||
this.lastBeat = Date.now();
|
||||
|
||||
@@ -617,6 +617,7 @@ class SignalingServer implements MessageComponentInterface
|
||||
'from' => $fromId,
|
||||
'data' => [
|
||||
'text' => $data['data']['text'] ?? '',
|
||||
'envelope' => $data['data']['envelope'] ?? null,
|
||||
'timestamp' => time() * 1000,
|
||||
],
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user