fix: 移除冲突的 node-polyfills(改用 CDN 加载 WebTorrent)+ 加 P2P 进度翻译与顶栏活动指示灯
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { TorrentFileEntry } from '~/types/api'
|
||||
import { formatBytes } from '~/utils/format'
|
||||
import { translateText } from '~/utils/i18n'
|
||||
import { readFileBytesClient, type FileProgress } from '~/utils/clientTorrent'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -62,7 +63,9 @@ async function load() {
|
||||
timeoutMs: 25000,
|
||||
onProgress: (p: FileProgress) => {
|
||||
progress.value = p.progress
|
||||
progressText.value = p.message
|
||||
progressText.value = p.peers > 0
|
||||
? `${translateText('下载中')} ${Math.round(p.progress * 100)}% · ${p.peers} ${translateText('个节点')}`
|
||||
: translateText('正在连接 P2P 节点…')
|
||||
}
|
||||
})
|
||||
const mime = MIME[extOf(current.value?.path || '')] || 'application/octet-stream'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { TorrentFileEntry, TorrentView } from '~/types/api'
|
||||
import { translateText } from '~/utils/i18n'
|
||||
import { resolveMetadataClient, type ResolveProgress } from '~/utils/clientTorrent'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -60,6 +61,16 @@ function applyResolved(res: { files: TorrentFileEntry[]; size: number; name: str
|
||||
emit('resolved', res)
|
||||
}
|
||||
|
||||
function resolveStatusText(p: ResolveProgress): string {
|
||||
if (p.phase === 'connecting') {
|
||||
return p.peers > 0
|
||||
? `${translateText('已连接')} ${p.peers} ${translateText('个节点,正在获取元数据…')}`
|
||||
: translateText('正在通过 WebRTC 寻找 P2P 节点…')
|
||||
}
|
||||
if (p.phase === 'metadata') return translateText('已拿到元数据,正在整理文件清单…')
|
||||
return `${translateText('共')} ${p.peers} ${translateText('个节点')}`
|
||||
}
|
||||
|
||||
async function doResolve() {
|
||||
resolving.value = true
|
||||
resolveStatus.value = ''
|
||||
@@ -77,7 +88,7 @@ async function doResolve() {
|
||||
const meta = await resolveMetadataClient(local.value.magnet, {
|
||||
timeoutMs: 30000,
|
||||
onProgress: (p: ResolveProgress) => {
|
||||
resolveStatus.value = p.message
|
||||
resolveStatus.value = resolveStatusText(p)
|
||||
}
|
||||
})
|
||||
applyResolved(meta)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { locale, setLocale, type Locale } from '~/utils/i18n'
|
||||
import { locale, setLocale, translateText, type Locale } from '~/utils/i18n'
|
||||
import { p2pActive } from '~/utils/clientTorrent'
|
||||
|
||||
const route = useRoute()
|
||||
const health = useHealth()
|
||||
@@ -50,6 +51,10 @@ async function logout() {
|
||||
<i class="dot" />
|
||||
{{ health?.webTorrent ? '磁力解析就绪' : '磁力解析(缓存模式)' }}
|
||||
</span>
|
||||
<span class="pill hide-sm" :class="p2pActive ? 'on' : 'dim'">
|
||||
<i class="dot" />
|
||||
{{ p2pActive ? translateText('P2P 活动中') : translateText('P2P 空闲') }}
|
||||
</span>
|
||||
<NuxtLink to="/settings" class="pill" :class="mongoPill.cls">
|
||||
<i class="dot" />
|
||||
{{ mongoPill.text }}
|
||||
|
||||
@@ -28,15 +28,61 @@ function withWsTrackers(magnet: string): string {
|
||||
return magnet + sep + add.map((t) => `tr=${encodeURIComponent(t)}`).join('&')
|
||||
}
|
||||
|
||||
// 浏览器端 WebTorrent 以 CDN 的 UMD 包形式按需加载(避免把 webtorrent 打进 Vite 客户端,
|
||||
// 同时绕开 Vite 8 与 node-polyfills 的 peer 依赖冲突)。优先 jsDelivr,失败再试 unpkg。
|
||||
const WT_CDN_URLS = [
|
||||
'https://cdn.jsdelivr.net/npm/webtorrent@3.0.21/dist/webtorrent.min.js',
|
||||
'https://unpkg.com/webtorrent@3.0.21/dist/webtorrent.min.js'
|
||||
]
|
||||
|
||||
let wtPromise: Promise<any> | null = null
|
||||
function loadWebTorrent(): Promise<any> {
|
||||
if (!wtPromise) {
|
||||
// 仅在浏览器运行时按需加载,Vite 会将其单独拆包;服务端不会执行到此处
|
||||
wtPromise = import('webtorrent').then((m) => (m as any).default || m)
|
||||
wtPromise = new Promise((resolve, reject) => {
|
||||
if (typeof window === 'undefined') {
|
||||
reject(new Error('WebTorrent 只能在浏览器中运行'))
|
||||
return
|
||||
}
|
||||
const existing = (window as any).WebTorrent
|
||||
if (existing) {
|
||||
resolve(existing)
|
||||
return
|
||||
}
|
||||
const tryLoad = (i: number) => {
|
||||
if (i >= WT_CDN_URLS.length) {
|
||||
reject(new Error('无法从 CDN 加载 WebTorrent(可能被网络或 CSP 拦截)'))
|
||||
return
|
||||
}
|
||||
const script = document.createElement('script')
|
||||
script.src = WT_CDN_URLS[i]
|
||||
script.async = true
|
||||
script.onload = () => {
|
||||
const WT = (window as any).WebTorrent
|
||||
if (WT) resolve(WT)
|
||||
else tryLoad(i + 1)
|
||||
}
|
||||
script.onerror = () => tryLoad(i + 1)
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
tryLoad(0)
|
||||
})
|
||||
}
|
||||
return wtPromise
|
||||
}
|
||||
|
||||
// 当前活跃的浏览器 P2P 会话数,用于顶栏指示
|
||||
import { ref } from 'vue'
|
||||
export const p2pActive = ref(false)
|
||||
let activeSessions = 0
|
||||
function enterSession() {
|
||||
activeSessions++
|
||||
p2pActive.value = activeSessions > 0
|
||||
}
|
||||
function leaveSession() {
|
||||
activeSessions = Math.max(0, activeSessions - 1)
|
||||
p2pActive.value = activeSessions > 0
|
||||
}
|
||||
|
||||
function waitForMetadata(torrent: any, timeoutMs: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (torrent.metadata || torrent.ready) return resolve()
|
||||
@@ -79,6 +125,7 @@ export async function resolveMetadataClient(
|
||||
const timeoutMs = opts.timeoutMs ?? 30000
|
||||
const WebTorrent = await loadWebTorrent()
|
||||
const client = new WebTorrent()
|
||||
enterSession()
|
||||
try {
|
||||
const torrent = client.add(withWsTrackers(magnet))
|
||||
const report = (phase: ResolveProgress['phase'], message?: string) => {
|
||||
@@ -103,6 +150,7 @@ export async function resolveMetadataClient(
|
||||
onProgress?.({ phase: 'done', peers: torrent.numPeers ?? 0, message: `共 ${files.length} 个文件` })
|
||||
return { name: String(torrent.name || ''), files, size: files.reduce((s, f) => s + f.length, 0) }
|
||||
} finally {
|
||||
leaveSession()
|
||||
try {
|
||||
client.destroy()
|
||||
} catch {
|
||||
@@ -120,6 +168,7 @@ export async function readFileBytesClient(
|
||||
const timeoutMs = opts.timeoutMs ?? 25000
|
||||
const WebTorrent = await loadWebTorrent()
|
||||
const client = new WebTorrent()
|
||||
enterSession()
|
||||
try {
|
||||
const torrent = client.add(withWsTrackers(magnet))
|
||||
await waitForMetadata(torrent, timeoutMs)
|
||||
@@ -153,6 +202,7 @@ export async function readFileBytesClient(
|
||||
})
|
||||
return ab
|
||||
} finally {
|
||||
leaveSession()
|
||||
try {
|
||||
client.destroy()
|
||||
} catch {
|
||||
|
||||
@@ -11,6 +11,21 @@ export const zh2en: Record<string, string> = {
|
||||
'MONGO 未连接': 'MONGO disconnected',
|
||||
'磁力解析就绪': 'Magnet parsing ready',
|
||||
'磁力解析(缓存模式)': 'Magnet parsing (cache mode)',
|
||||
|
||||
// ===== 浏览器端 P2P 进度文案 =====
|
||||
'P2P 活动中': 'P2P active',
|
||||
'P2P 空闲': 'P2P idle',
|
||||
'已连接': 'Connected',
|
||||
'个节点,正在获取元数据…': ' peers, fetching metadata…',
|
||||
'正在通过 WebRTC 寻找 P2P 节点…': 'Looking for P2P nodes via WebRTC…',
|
||||
'已拿到元数据,正在整理文件清单…': 'Metadata received, listing files…',
|
||||
'共': 'Total',
|
||||
'个节点': ' peers',
|
||||
'个文件': ' files',
|
||||
'下载中': 'Downloading',
|
||||
'正在连接 P2P 节点…': 'Connecting to P2P nodes…',
|
||||
'正在通过 WebRTC 从做种节点拉取图片…': 'Fetching image from seeders via WebRTC…',
|
||||
'完成': 'Done',
|
||||
'本地自托管 · 单进程 Nuxt 单体应用': 'Self-hosted · single-process Nuxt app',
|
||||
'种子只做解析与预览,文件内容由你自己的下载器拉取': 'Torrents are only parsed and previewed; file contents are fetched by your own download client',
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import nodePolyfills from 'vite-plugin-node-polyfills'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: false },
|
||||
@@ -29,12 +27,6 @@ export default defineNuxtConfig({
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
plugins: [
|
||||
nodePolyfills({
|
||||
globals: { Buffer: true, global: true, process: true },
|
||||
protocolImports: true
|
||||
})
|
||||
],
|
||||
server: {
|
||||
fs: { strict: false }
|
||||
}
|
||||
|
||||
+1
-2
@@ -20,7 +20,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite-plugin-node-polyfills": "^0.23.0"
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user