feat: harden RSS article opening and upgrade CI runtime#18
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens RSS article opening by routing it through main-process IPC guarded by an external URL policy (removing direct window.open usage in the renderer), while also improving the RSS reader UX and modernizing CI/release workflows.
Changes:
- Add
openRssArticleto the RSS facade/preload layer and use it fromRssReaderViewinstead ofwindow.open. - Add an inline unread-only “已读” action in the RSS reader list.
- Extend external URL policy + IPC handlers/tests for RSS article opening; upgrade GitHub Actions runtimes and update the OAuth setup guide.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/renderer/src/views/RssReaderView.vue | Uses openRssArticle for opening links; adds unread-only inline “已读” action. |
| src/renderer/src/views/RssReaderView.facade.test.mjs | Updates facade contract assertions to include openRssArticle and no window.open. |
| src/renderer/src/styles.css | Adds small layout/styles for RSS post row and “已读” button. |
| src/renderer/src/composables/useRssActions.mjs | Adds openRssArticle action with URL assertion and forwards to API. |
| src/renderer/src/composables/useRssActions.test.mjs | Covers openRssArticle contract and validates missing-url rejection. |
| src/main/preload.js | Exposes openRssArticle via IPC channel rss:openArticle. |
| src/main/preload.test.js | Verifies preload routes rss:openArticle and exposes openRssArticle. |
| src/main/policies/externalUrlPolicy.js | Adds rssArticle rule allowing http:/https:. |
| src/main/policies/externalUrlPolicy.test.js | Tests RSS article rule protocol allowance + credential rejection. |
| src/main/ipc/rssIpc.js | Adds rss:openArticle IPC handler that validates URL policy and calls shell.openExternal. |
| src/main/ipc/rssIpc.test.js | Tests allow/deny behavior for rss:openArticle guard and open call. |
| src/main/ipc.js | Wires RSS open handler with evaluateExternalUrl, shell.openExternal, and EXTERNAL_URL_RULES.rssArticle. |
| docs/guides/github-oauth-app-setup.md | Rewrites OAuth setup guide with explicit Device Flow requirements. |
| .github/workflows/ci.yml | Upgrades actions and Node runtime; sets Node24 actions env flag. |
| .github/workflows/release-windows.yml | Upgrades actions and Node runtime; sets Node24 actions env flag. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function openPost(subscription, post) { | ||
| if (!post?.link) { | ||
| message.value = "该文章缺少可打开的链接。"; | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await openRssArticle({ url: post.link }); | ||
| } catch (error) { | ||
| message.value = `打开文章失败:${String(error?.message || error)}`; | ||
| return; | ||
| } | ||
|
|
||
| await markPostAsRead(subscription, post); | ||
| } |
There was a problem hiding this comment.
openPost now sets message on failures, but it never clears message on success. This can leave a previous “打开文章失败…” message visible even after a later click successfully opens an article. Consider clearing message.value before attempting to open (or after a successful open) so the UI reflects the latest action.
| }); | ||
| ipcMain.handle('rss:openArticle', async (_event, payload) => { | ||
| const decision = evaluateExternalUrl(payload?.url, externalUrlRule); | ||
| if (!decision.allowed) { |
There was a problem hiding this comment.
rss:openArticle throws the same “外部链接不受信任” error for all failures, including malformed URLs where evaluateExternalUrl returns reason: 'INVALID_URL'. That produces confusing renderer messages like “外部链接不受信任:INVALID_URL” for a feed item with a bad link. Consider special-casing INVALID_URL (and possibly SCHEME_NOT_ALLOWED) to throw a clearer error (e.g., “链接格式无效”) so users can distinguish an invalid link from a blocked/untrusted one.
| if (!decision.allowed) { | |
| if (!decision.allowed) { | |
| if (decision.reason === 'INVALID_URL') { | |
| throw new Error('链接格式无效'); | |
| } | |
| if (decision.reason === 'SCHEME_NOT_ALLOWED') { | |
| throw new Error('链接协议不被允许'); | |
| } |
Summary
openRssArticlethrough preload/composables to remove direct rendererwindow.openusage已读actions and keeping title click open behavior consistent with read-state updatesTest Plan