feat: 添加版本比较和排序功能,优化获取更新快照的逻辑

This commit is contained in:
QLHazyCoder
2026-04-18 17:40:05 +08:00
parent 8e3e085f3d
commit 5e1d89b3c5
4 changed files with 203 additions and 8 deletions
+26 -6
View File
@@ -184,6 +184,26 @@
};
}
function getComparableReleaseVersion(release) {
const fallbackFamily = release?.family === VERSION_FAMILY_PRO
? VERSION_FAMILY_PRO
: VERSION_FAMILY_LEGACY;
const displayVersion = String(release?.displayVersion || '').trim();
if (displayVersion) {
return displayVersion;
}
return formatDisplayVersion(release?.version || '', fallbackFamily);
}
function sortReleasesByVersion(releases = []) {
return [...releases].sort((left, right) => (
compareVersions(
getComparableReleaseVersion(right),
getComparableReleaseVersion(left)
)
));
}
function readCache() {
try {
const raw = localStorage.getItem(CACHE_KEY);
@@ -200,7 +220,7 @@
return null;
}
return parsed.releases;
return sortReleasesByVersion(parsed.releases).slice(0, MAX_RELEASES);
} catch (error) {
return null;
}
@@ -243,12 +263,12 @@
const releases = payload
.filter((release) => release && !release.draft && !release.prerelease)
.map(sanitizeRelease)
.filter(Boolean)
.sort((left, right) => compareVersions(right.version, left.version))
.slice(0, MAX_RELEASES);
.filter(Boolean);
writeCache(releases);
return releases;
const sortedReleases = sortReleasesByVersion(releases).slice(0, MAX_RELEASES);
writeCache(sortedReleases);
return sortedReleases;
} catch (error) {
if (error?.name === 'AbortError') {
throw new Error('GitHub Releases 请求超时');
+173
View File
@@ -0,0 +1,173 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('sidepanel/update-service.js', 'utf8');
function createUpdateService(options = {}) {
const manifest = options.manifest || {
version: '2.0',
version_name: 'Pro2.0',
};
const cache = new Map();
const windowObject = {};
let fetchCalls = 0;
const localStorage = {
getItem(key) {
return cache.has(key) ? cache.get(key) : null;
},
setItem(key, value) {
cache.set(key, String(value));
},
};
if (options.cachedSnapshot) {
cache.set(
'multipage-release-snapshot-v1',
JSON.stringify(options.cachedSnapshot)
);
}
const fetchImpl = options.fetchImpl || (async () => ({
ok: true,
async json() {
return [];
},
}));
const wrappedFetch = async (...args) => {
fetchCalls += 1;
return fetchImpl(...args);
};
const api = new Function(
'window',
'localStorage',
'fetch',
'chrome',
'AbortController',
'setTimeout',
'clearTimeout',
`${source}; return window.SidepanelUpdateService;`
)(
windowObject,
localStorage,
wrappedFetch,
{
runtime: {
getManifest() {
return manifest;
},
},
},
AbortController,
setTimeout,
clearTimeout
);
return {
api,
getFetchCalls() {
return fetchCalls;
},
};
}
test('getReleaseSnapshot keeps Pro releases ahead of legacy v releases', async () => {
const { api } = createUpdateService({
manifest: {
version: '2.0',
version_name: 'Pro2.0',
},
fetchImpl: async () => ({
ok: true,
async json() {
return [
{
tag_name: 'v11.0.0',
name: 'v11.0.0',
html_url: 'https://example.com/v11.0.0',
published_at: '2026-04-17T00:00:00.000Z',
body: '- legacy release',
draft: false,
prerelease: false,
},
{
tag_name: 'Pro2.4',
name: 'Pro2.4',
html_url: 'https://example.com/Pro2.4',
published_at: '2026-04-18T00:00:00.000Z',
body: '- pro release',
draft: false,
prerelease: false,
},
{
tag_name: 'Pro2.0',
name: 'Pro2.0',
html_url: 'https://example.com/Pro2.0',
published_at: '2026-04-16T00:00:00.000Z',
body: '- current release',
draft: false,
prerelease: false,
},
];
},
}),
});
const snapshot = await api.getReleaseSnapshot({ force: true });
assert.equal(snapshot.status, 'update-available');
assert.equal(snapshot.localVersion, 'Pro2.0');
assert.equal(snapshot.latestVersion, 'Pro2.4');
assert.deepEqual(
snapshot.newerReleases.map((release) => release.displayVersion),
['Pro2.4']
);
});
test('getReleaseSnapshot reorders cached releases before choosing latest version', async () => {
const { api, getFetchCalls } = createUpdateService({
manifest: {
version: '2.0',
version_name: 'Pro2.0',
},
cachedSnapshot: {
fetchedAt: Date.now(),
releases: [
{
version: '11.0.0',
displayVersion: 'v11.0.0',
family: 'legacy',
title: '',
url: 'https://example.com/v11.0.0',
publishedAt: '2026-04-17T00:00:00.000Z',
notes: [],
},
{
version: '2.4',
displayVersion: 'Pro2.4',
family: 'pro',
title: '',
url: 'https://example.com/Pro2.4',
publishedAt: '2026-04-18T00:00:00.000Z',
notes: [],
},
],
},
fetchImpl: async () => {
throw new Error('should not fetch when cache is fresh');
},
});
const snapshot = await api.getReleaseSnapshot();
assert.equal(getFetchCalls(), 0);
assert.equal(snapshot.status, 'update-available');
assert.equal(snapshot.latestVersion, 'Pro2.4');
assert.deepEqual(
snapshot.newerReleases.map((release) => release.displayVersion),
['Pro2.4']
);
});
+2 -1
View File
@@ -28,7 +28,7 @@
### 2.1 Sidepanel
[sidepanel/sidepanel.html](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.html) + [sidepanel/sidepanel.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.js)
[sidepanel/sidepanel.html](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.html) + [sidepanel/sidepanel.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.js) + [sidepanel/update-service.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/update-service.js)
职责:
@@ -38,6 +38,7 @@
- 接收后台广播并更新 UI
- 动态渲染步骤列表
- 在日志区通过“记录”按钮打开独立的邮箱记录覆盖层,并展示成功/失败/停止/重试统计与分页列表
- 查询 GitHub Releases 并展示更新卡片;当前更新服务会区分 `Pro` 与 legacy `v` 两个版本族,排序时优先保持版本族语义一致,同时会在读取缓存后重新排序,避免旧缓存把 `v` 版本误显示为比 `Pro` 更新
### 2.2 Background Service Worker
+2 -1
View File
@@ -116,7 +116,7 @@
- `sidepanel/sidepanel.css`:侧边栏样式文件。
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区提供“记录”按钮并挂接邮箱记录覆盖层,同时新增共享“验证码重发”次数输入,并加载 `managed-alias-utils.js`,把旧的“邮箱前缀”字段语义改为“别名基邮箱”。
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、独立本地同步配置、共享验证码自动重发次数配置与广播接收,并装配 Hotmail / iCloud / LuckMail / 邮箱记录面板 manager;同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接到共享别名逻辑上。
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询与版本展示。
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询`Pro` / `v` 双版本族排序、缓存读取与版本展示。
## `tests/`
@@ -179,5 +179,6 @@
- `tests/step9-cpa-mode.test.js`:测试本地 CPA 平台回调跳过策略判断,当前作用于步骤 10。
- `tests/step9-localhost-cleanup-scope.test.js`:测试 localhost callback 与路径前缀残留页的精确清理范围,当前对应步骤 10 收尾。
- `tests/step9-status-diagnostics.test.js`:测试平台回调成功判定、红色错误态过滤,以及成功徽标与失败提示并存时的优先级,当前对应步骤 10。
- `tests/update-service.test.js`:测试侧边栏更新检查服务在 `Pro` / `v` 混合版本和旧缓存场景下都能按版本族正确排序,不会把 legacy `v` 版本误显示成最新可升级版本。
- `tests/verification-stop-propagation.test.js`:测试验证码流程在 Stop 场景下的错误传播与不中途降级。
- `tests/verification-flow-polling.test.js`:测试 2925 长轮询参数、验证码提交流程中的 `beforeSubmit` 钩子执行顺序,以及步骤 8 提交验证码后进入手机号页时不会误判成功。