移植桌面端 2025-03-22 之后的更新到移动端,保留移动端自身布局: - 补齐管理子路径鉴权参数:缩略图、在线图片、/cache 请求带上 AuthCode; 原线上靠临时热补丁脚本 online-reader-auth-fix.js 绕过,现由源码原生支持。 - 断线自动重连:新增 src/utils/taskSocket.js,指数退避重连并在重连后重新同步 任务列表与用量;App.vue 接入页面可见性/网络恢复事件,Side.vue 显示连接状态。 - 未完成任务重试:新增 retryGallery action 与重试按钮;补 "等待压缩" 状态。 - 图片容错:新增 src/utils/progressiveImages.js,单图加载失败不再阻塞后续页, 支持单图重试。 - store 修复:收藏列表残留(slice→splice)、action 内改 context.state、 gid 判空(null→undefined)、deleteTask 跳过下一元素、resetUndone 用 state 授权码、 axios 全局错误拦截器、getShortname 简化。 - 下载链接统一由 buildGalleryDownloadUrl 生成(按 gid 而非名字)。 - 构建配置:base 改为 /asserts/mobile/、固定产物名 index.js/index.css、新增 .gitignore。 - 升级 Vite 8.3.0 + @vitejs/plugin-vue 6.0.9,npm audit 0 漏洞。 - 新增 tests/(11 项 Node 测试,全部通过)。
64 lines
2.3 KiB
JavaScript
64 lines
2.3 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import {createTaskSocket} from '../src/utils/taskSocket.js'
|
|
|
|
function fixture() {
|
|
const sockets = [], states = [], messages = [], timers = new Map()
|
|
let id = 0, opens = 0
|
|
const manager = createTaskSocket({url: 'wss://example.invalid',
|
|
onOpen: () => opens++, onState: value => states.push(value), onMessage: event => messages.push(event.data),
|
|
setTimer: (fn, delay) => { timers.set(++id, {fn, delay}); return id },
|
|
clearTimer: key => timers.delete(key),
|
|
createSocket: () => {
|
|
const socket = {sent: [], send(value) { this.sent.push(value) }, close() { this.closed = true }}
|
|
sockets.push(socket)
|
|
return socket
|
|
}
|
|
})
|
|
function tick(delay) {
|
|
const entry = [...timers].find(([, timer]) => timer.delay === delay)
|
|
assert.ok(entry, `expected timer ${delay}`)
|
|
timers.delete(entry[0]); entry[1].fn()
|
|
}
|
|
return {manager, sockets, states, messages, timers, tick, opens: () => opens}
|
|
}
|
|
|
|
test('reconnects and refreshes after each successful connection', () => {
|
|
const f = fixture(); f.manager.start()
|
|
f.sockets[0].onopen()
|
|
assert.deepEqual(f.sockets[0].sent, ['DownloaderWebsocket'])
|
|
f.sockets[0].onclose()
|
|
f.tick(1000)
|
|
f.sockets[1].onopen()
|
|
assert.equal(f.opens(), 2)
|
|
assert.equal(f.states.at(-1), 'connected')
|
|
assert.equal(f.timers.size, 0)
|
|
f.manager.stop()
|
|
})
|
|
|
|
test('backs off and times out connection attempts', () => {
|
|
const f = fixture(); f.manager.start()
|
|
f.tick(10000)
|
|
assert.equal(f.sockets[0].closed, true)
|
|
f.tick(1000); f.sockets[1].onerror(); f.tick(2000)
|
|
f.sockets[2].onerror(); f.tick(4000)
|
|
assert.equal(f.sockets.length, 4)
|
|
f.manager.stop()
|
|
})
|
|
|
|
test('stop cancels timers and ignores callbacks from replaced sockets', () => {
|
|
const f = fixture(); f.manager.start()
|
|
const oldOpen = f.sockets[0].onopen, oldMessage = f.sockets[0].onmessage
|
|
f.sockets[0].onerror()
|
|
f.manager.stop()
|
|
assert.equal(f.timers.size, 0)
|
|
f.manager.start()
|
|
oldOpen(); oldMessage({data: 'stale'})
|
|
assert.equal(f.opens(), 0)
|
|
assert.deepEqual(f.messages, [])
|
|
f.sockets[1].onopen()
|
|
f.sockets[1].onmessage({data: 'current'})
|
|
assert.deepEqual(f.messages, ['current'])
|
|
f.manager.stop()
|
|
})
|