feat: 添加版本比较和排序功能,优化获取更新快照的逻辑
This commit is contained in:
@@ -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 请求超时');
|
||||
|
||||
@@ -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
@@ -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
@@ -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 提交验证码后进入手机号页时不会误判成功。
|
||||
|
||||
Reference in New Issue
Block a user