修复wss连接、一些未知错误
This commit is contained in:
File diff suppressed because one or more lines are too long
17
frontend/dist/assets/index-DE3lDjdM.js
vendored
17
frontend/dist/assets/index-DE3lDjdM.js
vendored
File diff suppressed because one or more lines are too long
17
frontend/dist/assets/index-DPzeYqvr.js
vendored
Normal file
17
frontend/dist/assets/index-DPzeYqvr.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AirShare Pro</title>
|
||||
<script type="module" crossorigin src="/assets/index-DE3lDjdM.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CQ9sinZs.css">
|
||||
<script type="module" crossorigin src="/assets/index-DPzeYqvr.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C-7tVt-S.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -35,6 +35,7 @@ const connectedPeer = ref({
|
||||
name: '--',
|
||||
type: '等待连接',
|
||||
deviceId: '',
|
||||
networkGroupKey: '',
|
||||
})
|
||||
const transferItems = ref([])
|
||||
const relayServer = ref('/ws')
|
||||
@@ -82,6 +83,17 @@ const pendingTransferItems = computed(() =>
|
||||
)
|
||||
|
||||
const hasPendingItems = computed(() => pendingTransferItems.value.length > 0)
|
||||
const currentTransferNetworkHint = computed(() => {
|
||||
if (!connectedPeer.value.deviceId) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (isSameLocalNetwork(connectedPeer.value.networkGroupKey) || hasTurnRelayConfigured()) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return '当前是跨网络访问,未配置 TURN 时实时通道可能失败。文本和小文件可回退中转,大文件建议使用 MinIO。'
|
||||
})
|
||||
|
||||
watch(
|
||||
theme,
|
||||
@@ -208,7 +220,7 @@ async function registerCurrentDevice() {
|
||||
device_id: deviceId,
|
||||
name: deviceName,
|
||||
type: deviceType,
|
||||
network_group_key: window.location.hostname || 'local',
|
||||
network_group_key: deriveNetworkGroupKey(),
|
||||
})
|
||||
|
||||
localStorage.setItem(DEVICE_ID_KEY, device.id)
|
||||
@@ -269,7 +281,7 @@ async function refreshCandidates() {
|
||||
description: `${formatDeviceType(device.type)} · 最近活跃 ${formatRelativeTime(device.last_seen_at)}`,
|
||||
icon: mapDeviceIcon(device.type),
|
||||
connectionType:
|
||||
device.network_group_key && device.network_group_key === window.location.hostname
|
||||
isSameLocalNetwork(device.network_group_key)
|
||||
? '局域网直连优先'
|
||||
: '点对点传输',
|
||||
}))
|
||||
@@ -412,6 +424,7 @@ function connectToPeer(device) {
|
||||
name: device.name,
|
||||
type: device.connectionType || device.type || '点对点传输',
|
||||
deviceId: device.deviceId || device.id || '',
|
||||
networkGroupKey: device.network_group_key || '',
|
||||
}
|
||||
isWaitingRoom.value = false
|
||||
generatedRoomCode.value = '----'
|
||||
@@ -439,6 +452,7 @@ function ensurePeerSession(device, preserveItems = false) {
|
||||
name: device.name,
|
||||
type: device.connectionType || device.type || '点对点传输',
|
||||
deviceId: nextDeviceId,
|
||||
networkGroupKey: device.network_group_key || '',
|
||||
}
|
||||
isWaitingRoom.value = false
|
||||
generatedRoomCode.value = '----'
|
||||
@@ -457,6 +471,7 @@ function closeTransfer() {
|
||||
name: '--',
|
||||
type: '等待连接',
|
||||
deviceId: '',
|
||||
networkGroupKey: '',
|
||||
}
|
||||
viewMode.value = 'main'
|
||||
connectedPeer.value.baseType = '等待连接'
|
||||
@@ -733,7 +748,7 @@ async function copyTextItem(id) {
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(item.text)
|
||||
await copyToClipboard(item.text)
|
||||
item.copied = true
|
||||
|
||||
window.setTimeout(() => {
|
||||
@@ -748,6 +763,33 @@ async function copyTextItem(id) {
|
||||
}
|
||||
}
|
||||
|
||||
async function copyToClipboard(value) {
|
||||
if (navigator.clipboard?.writeText && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(value)
|
||||
return
|
||||
}
|
||||
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = value
|
||||
textarea.setAttribute('readonly', 'readonly')
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.top = '0'
|
||||
textarea.style.left = '-9999px'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
textarea.setSelectionRange(0, textarea.value.length)
|
||||
|
||||
try {
|
||||
if (!document.execCommand('copy')) {
|
||||
throw new Error('copy command failed')
|
||||
}
|
||||
} finally {
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
}
|
||||
|
||||
function simulateUpload(id) {
|
||||
clearUploadTimer(id)
|
||||
|
||||
@@ -1169,6 +1211,48 @@ function buildRelayServerPath() {
|
||||
return `${protocol}//${window.location.host}/ws`
|
||||
}
|
||||
|
||||
function hasTurnRelayConfigured() {
|
||||
return Array.isArray(runtimeConfig.value?.turn_urls) && runtimeConfig.value.turn_urls.some((item) => String(item || '').trim())
|
||||
}
|
||||
|
||||
function deriveNetworkGroupKey() {
|
||||
const hostname = String(window.location.hostname || '').trim().toLowerCase()
|
||||
if (!hostname) {
|
||||
return 'local'
|
||||
}
|
||||
|
||||
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname.endsWith('.local')) {
|
||||
return hostname
|
||||
}
|
||||
|
||||
if (isPrivateIPv4Host(hostname)) {
|
||||
return hostname
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function isSameLocalNetwork(networkGroupKey) {
|
||||
const localNetworkGroupKey = deriveNetworkGroupKey()
|
||||
return !!localNetworkGroupKey && networkGroupKey === localNetworkGroupKey
|
||||
}
|
||||
|
||||
function isPrivateIPv4Host(hostname) {
|
||||
const parts = hostname.split('.')
|
||||
if (parts.length !== 4 || parts.some((part) => !/^\d+$/.test(part))) {
|
||||
return false
|
||||
}
|
||||
|
||||
const [first, second] = parts.map((part) => Number(part))
|
||||
if (first === 10 || first === 127) {
|
||||
return true
|
||||
}
|
||||
if (first === 192 && second === 168) {
|
||||
return true
|
||||
}
|
||||
return first === 172 && second >= 16 && second <= 31
|
||||
}
|
||||
|
||||
function buildIceServers(configValue) {
|
||||
const urls = Array.isArray(configValue?.turn_urls)
|
||||
? configValue.turn_urls.map((item) => String(item || '').trim()).filter(Boolean)
|
||||
@@ -1235,12 +1319,32 @@ function syncConnectionTypeLabel(status = '') {
|
||||
}
|
||||
}
|
||||
|
||||
function shouldRecreateRealtimeTransport(targetDeviceId) {
|
||||
if (!peerConnection || currentRtcPeerId !== targetDeviceId) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (peerConnection.signalingState === 'closed') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (['failed', 'disconnected', 'closed'].includes(peerConnection.connectionState)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (['failed', 'disconnected', 'closed'].includes(peerConnection.iceConnectionState)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !controlChannel || controlChannel.readyState === 'closed'
|
||||
}
|
||||
|
||||
async function ensureRealtimeTransport(targetDeviceId, options = {}) {
|
||||
if (!targetDeviceId || !supportsWebRTC()) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!peerConnection || currentRtcPeerId !== targetDeviceId) {
|
||||
if (shouldRecreateRealtimeTransport(targetDeviceId)) {
|
||||
teardownRealtimeTransport()
|
||||
createPeerConnection(targetDeviceId)
|
||||
}
|
||||
@@ -1582,6 +1686,7 @@ function buildPeerSessionFromDevice(deviceId, fallbackConnectionType = '等待
|
||||
name: knownDevice?.name || `设备 ${shortId(deviceId)}`,
|
||||
type: formatDeviceType(knownDevice?.type || 'desktop'),
|
||||
connectionType: fallbackConnectionType,
|
||||
network_group_key: knownDevice?.network_group_key || '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1797,12 +1902,35 @@ function handleRelayMessage(raw) {
|
||||
|
||||
if (envelope.type === 'transfer.file') {
|
||||
handleIncomingTransferFile(envelope)
|
||||
return
|
||||
}
|
||||
|
||||
if (envelope.type === 'peer.session.closed') {
|
||||
handlePeerSessionClosed(envelope)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
function handlePeerSessionClosed(envelope) {
|
||||
const senderDeviceId = envelope.device_id || ''
|
||||
if (!senderDeviceId || connectedPeer.value.deviceId !== senderDeviceId) {
|
||||
return
|
||||
}
|
||||
|
||||
teardownRealtimeTransport()
|
||||
resetTransferItems()
|
||||
connectedPeer.value = {
|
||||
name: '--',
|
||||
type: '绛夊緟杩炴帴',
|
||||
baseType: '绛夊緟杩炴帴',
|
||||
deviceId: '',
|
||||
networkGroupKey: '',
|
||||
}
|
||||
viewMode.value = 'main'
|
||||
}
|
||||
|
||||
function handleIncomingTransferCreated(envelope) {
|
||||
const payload = envelope.payload || {}
|
||||
const senderDeviceId = envelope.device_id || payload.sender_device_id || ''
|
||||
@@ -2178,7 +2306,7 @@ refreshCandidates = async function refreshCandidatesOverride() {
|
||||
description: `${formatDeviceType(device.type)} · 最近活跃 ${formatRelativeTime(device.last_seen_at)}`,
|
||||
icon: mapDeviceIcon(device.type),
|
||||
connectionType:
|
||||
device.network_group_key && device.network_group_key === window.location.hostname
|
||||
isSameLocalNetwork(device.network_group_key)
|
||||
? '局域网直连优先'
|
||||
: '跨网络实时传输',
|
||||
}))
|
||||
@@ -2206,6 +2334,7 @@ connectToPeer = function connectToPeerOverride(device) {
|
||||
type: nextBaseType,
|
||||
baseType: nextBaseType,
|
||||
deviceId: nextDeviceId,
|
||||
networkGroupKey: device.network_group_key || '',
|
||||
}
|
||||
isWaitingRoom.value = false
|
||||
generatedRoomCode.value = '----'
|
||||
@@ -2231,6 +2360,7 @@ ensurePeerSession = function ensurePeerSessionOverride(device, preserveItems = f
|
||||
type: nextBaseType,
|
||||
baseType: nextBaseType,
|
||||
deviceId: nextDeviceId,
|
||||
networkGroupKey: device.network_group_key || '',
|
||||
}
|
||||
isWaitingRoom.value = false
|
||||
generatedRoomCode.value = '----'
|
||||
@@ -2243,6 +2373,10 @@ ensurePeerSession = function ensurePeerSessionOverride(device, preserveItems = f
|
||||
}
|
||||
|
||||
closeTransfer = function closeTransferOverride() {
|
||||
if (connectedPeer.value.deviceId) {
|
||||
relayToPeer('peer.session.closed', connectedPeer.value.deviceId, {})
|
||||
}
|
||||
|
||||
teardownRealtimeTransport()
|
||||
resetTransferItems()
|
||||
connectedPeer.value = {
|
||||
@@ -2250,6 +2384,7 @@ closeTransfer = function closeTransferOverride() {
|
||||
type: '等待连接',
|
||||
baseType: '等待连接',
|
||||
deviceId: '',
|
||||
networkGroupKey: '',
|
||||
}
|
||||
viewMode.value = 'main'
|
||||
}
|
||||
@@ -2709,6 +2844,7 @@ handleIncomingTransferFile = function handleIncomingTransferFileOverride(envelop
|
||||
:connection-type="connectedPeer.type"
|
||||
:has-pending-items="hasPendingItems"
|
||||
:items="transferItems"
|
||||
:network-hint="currentTransferNetworkHint"
|
||||
:peer-name="connectedPeer.name"
|
||||
@close="closeTransfer"
|
||||
@copy-item="copyTextItem"
|
||||
|
||||
@@ -12,6 +12,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
networkHint: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
@@ -84,6 +88,7 @@ function handleDrop(event) {
|
||||
<div class="connected-to">
|
||||
<h2>{{ peerName }}</h2>
|
||||
<p>{{ connectionType }}</p>
|
||||
<small v-if="networkHint" class="connection-hint">{{ networkHint }}</small>
|
||||
</div>
|
||||
<button class="close-btn" type="button" @click="$emit('close')">
|
||||
<LocalIcon name="close" size="20" />
|
||||
|
||||
@@ -708,6 +708,15 @@ input.room-code::placeholder {
|
||||
color: var(--success-green);
|
||||
}
|
||||
|
||||
.connection-hint {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
max-width: 42ch;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
Reference in New Issue
Block a user