Skip to content

fix(market): harden Auth login and catalog integration - #2566

Open
wislap wants to merge 1 commit into
Project-N-E-K-O:mainfrom
wislap:codex/upstream-main-20260729
Open

fix(market): harden Auth login and catalog integration#2566
wislap wants to merge 1 commit into
Project-N-E-K-O:mainfrom
wislap:codex/upstream-main-20260729

Conversation

@wislap

@wislap wislap commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

改动概述

本 PR 修复 N.E.K.O App 与 Auth / Plugin Market 集成中的登录误判、目录跨域和异步竞态问题:

  • 将 “Auth 已认证” 与 “Market 已就绪” 拆为独立状态;Market 的身份冲突、临时不可用或网络失败不再被统一误报为 token 无效。
  • 仅在 Auth 明确拒绝凭据时使会话失效;Market 暂时失败时保留 token,并支持安全重试。
  • 插件目录改走 N.E.K.O 本地同源只读桥接,避免浏览器直连 Market 引发 CORS 差异。
  • 为 Auth、Market、目录和安装下载链路增加结构化安全诊断。
  • 修复 logout、刷新、轮询和延迟响应之间可能覆盖新会话的竞态。
  • 补齐 8 个 locale 的新状态提示与回归测试。

边界审核

Authentication / Session

  • 只有经过 OIDC userinfo 确认且包含 sub 的响应才能建立 Auth 身份。
  • 401/403 等明确凭据拒绝与 409、5xx、超时等 Market 状态分开处理。
  • 状态、账户摘要和刷新写入均使用 access_token + session_id + refresh_generation 做 compare-and-swap,旧请求不能覆盖新会话。
  • logout 先同步清除本地 token、pending OAuth、回调与缓存,再异步撤销远端 token。
  • OAuth complete 在 token exchange 后、写入前再次校验 active state;迟到回调不会恢复已退出或已替换的会话。
  • 失败 token 的撤销不会阻塞本地会话清除,也不会误删撤销期间创建的新会话。

Proxy / Network

  • 目录代理仅允许固定的公开只读 GET 路由:插件列表、插件详情和版本列表。
  • 上游固定来自配置的 Market API,不接受用户提供的任意目标地址。
  • plugin_id 会进行路径编码,查询参数只透传到固定上游路径。
  • 代理禁止跟随重定向,避免固定上游边界被跳转绕过。
  • 插件包下载原有大小、校验和与包结构校验保持不变。

Logging / Sensitive Data

  • 日志只记录稳定错误分类、HTTP 状态、净化后的 request ID、耗时和 origin。
  • 不记录 access token、authorization code、Authorization header、响应正文、完整 query、签名下载 URL、代理凭据或异常原文。
  • request ID 与 origin 均经过字符、长度和凭据净化。

Frontend Async State

  • 登录轮询、Market 重试、状态与账户摘要请求都受 generation guard 保护。
  • logout / unmount 后到达的 complete、status、summary 和非 2xx 响应不会恢复旧状态或启动新重试。
  • pending、ready、identity conflict、forbidden、token rejected、unavailable 和 invalid response 均有独立 UI 状态。

范围排除

  • 不修改 Auth Platform 或 Plugin Market 服务端 schema。
  • 不执行历史账号的自动合并;真实身份冲突仍需管理员审计。
  • 未跟踪的 plugin/plugins/bilibili_danmaku/data/user_records/ 用户数据未纳入提交。

UI 前后对照

以下截图来自本地受控 API 状态下的实际构建:

改动前:

改动前:Market 登录失败仅显示重新登录入口

改动后:

改动后:明确显示正在确认 Auth 身份

Standards 审核

  • 仓库规范、认证 CAS、敏感日志、代理/SSRF、前端竞态和 i18n 审核通过。
  • 最终审核无剩余可操作项。

Spec / 信任边界审核

  • Auth rejection 的同会话、旧会话及撤销阻塞期间新建会话三种竞态均已覆盖。
  • logout、状态恢复、目录代理、日志脱敏、国际化和用户数据排除均符合需求。
  • 最终审核无剩余 Spec 或信任边界问题。

测试

  • .venv/bin/python -m pytest -q plugin/tests/integration/test_market_bridge_e2e.py57 passed
  • npm exec vitest run -- src/composables/useMarketAuth.test.ts src/api/market.test.ts13 passed
  • npm run check:i18n607 keys across 8 locales
  • npm run type-check — passed
  • npm run build-only — passed
  • Ruff、Python compile、git diff --check — passed

回归报告

不适用:未改动 app/main_logic/main_routers/memory/ 下的 Python 文件。

不拆分理由

不适用:按项目计数规则,仅 4 个非测试、非 locale、非新增文件计入,未超过 20。

Summary by CodeRabbit

  • 新功能
    • 市场登录流程新增待验证、凭证拒绝、权限不足、身份冲突及服务暂不可用等状态提示。
    • 市场暂时不可用时支持自动重试,并提供更清晰的本地化反馈。
    • 新增市场目录的同源访问能力,提升连接稳定性与安全性。
  • 问题修复
    • 修复登录、轮询、登出并发期间状态错误恢复的问题。
    • 改善市场认证失败、无效响应及插件下载失败时的处理。
  • 本地化
    • 补充英语、中文、日语、韩语、西班牙语、葡萄牙语及俄语提示。

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

本次更新将 Market catalog 改为本地同源桥接访问,扩展 OAuth 状态与并发会话处理,补充前端状态展示、多语言文案、错误诊断及大量集成与竞态测试,喵。

Changes

Market OAuth 与 catalog 桥接

Layer / File(s) Summary
Catalog 同源桥接
frontend/plugin-manager/src/api/market.ts, frontend/plugin-manager/src/api/market.test.ts, plugin/server/routes/market_bridge.py, plugin/tests/integration/test_market_bridge_e2e.py
catalog 请求改为经本地同源路径转发,代理控制响应头、编码路径并拒绝上游重定向,喵。
OAuth 状态与会话一致性
plugin/server/routes/market_bridge.py
OAuth status、complete、logout 与 token 刷新增加 pending/Market 状态、CAS 快照校验和活动会话检查,喵。
认证探测与错误诊断
plugin/server/routes/market_bridge.py
Auth/Market 探测、安装上报和下载流程增加错误分类、安全日志、耗时记录及明确异常映射,喵。
前端认证状态与界面
frontend/plugin-manager/src/composables/useMarketAuth.ts, frontend/plugin-manager/src/views/PluginList.vue, frontend/plugin-manager/src/i18n/locales/*
前端增加 OAuth 状态映射、可重试轮询、登出取消逻辑、pending 展示和多语言状态提示,喵。
OAuth 回归与竞态测试
frontend/plugin-manager/src/composables/useMarketAuth.test.ts, plugin/tests/integration/test_market_bridge_e2e.py
测试覆盖状态恢复、身份 pending、敏感日志、Market 异常响应及 logout 与刷新并发场景,喵。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: enhancement

Poem

同源桥接穿过喵
OAuth 状态轻轻摇喵
CAS 守住旧会话喵
pending 等待新回答喵
登出竞态散成星喵
猫娘验收成功喵

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了本 PR 对 Auth 登录与目录集成加固的核心改动喵
Description check ✅ Passed 描述基本覆盖了模板要求的改动概述、回归报告、拆分说明和测试信息,内容也足够具体喵
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 30, 2026
baseURL: `${baseUrl}/api/v1`,
// Keep catalog traffic same-origin. The local bridge forwards only the
// public, read-only catalog routes and avoids browser CORS failures.
baseURL: '/market/catalog/api/v1',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Catalog bridge bypasses dev proxy

When the plugin manager runs through Vite, requests under /market/catalog/api/v1 do not match the development proxy allowlist, so catalog lists, details, and versions never reach the plugin server and fail to load喵。

Knowledge Base Used:

Comment on lines +245 to +247
if (marketAuth.value.auth_state === 'pending') {
marketAuthBusy.value = true
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pending verification retries forever

When Auth repeatedly returns auth_state: 'pending' with retryable: true, each status request schedules another readiness retry and this branch keeps marketAuthBusy true without the five-minute deadline used by the OAuth polling loop, leaving the account control permanently loading with no way to restart login until the component unmounts喵。

Knowledge Base Used: Frontend Plugin Manager

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

此 PR 强化了 Auth/Market 集成并将目录流量迁移到本地同源桥接喵。

  • 拆分 Auth 身份与 Market 就绪状态,并为 OAuth、刷新、退出和延迟响应增加会话保护喵。
  • 新增只读目录代理、结构化脱敏诊断以及前端重试和状态提示喵。
  • 补充八种语言及前后端竞态、代理和日志回归测试喵。

Confidence Score: 3/5

合并前需要修复开发目录代理和无期限 pending 重试,否则目录加载或认证控件会进入不可用状态喵。

Vite 不会转发前端新使用的目录路径,同时持续返回 pending 的 Auth 状态会无限续订重试并保持忙碌状态,分别破坏开发环境目录访问和认证恢复流程喵。

Files Needing Attention: frontend/plugin-manager/src/api/market.ts, frontend/plugin-manager/vite.config.ts, frontend/plugin-manager/src/composables/useMarketAuth.ts

Important Files Changed

Filename Overview
plugin/server/routes/market_bridge.py Adds fixed catalog proxy routes, split Auth/Market state handling, session CAS guards, and sanitized diagnostics喵。
frontend/plugin-manager/src/composables/useMarketAuth.ts Adds generation-guarded status and retry transitions, but pending Auth verification has no terminal deadline and can leave the UI permanently busy喵。
frontend/plugin-manager/src/api/market.ts Moves catalog requests to the local bridge, but the new path is not forwarded by the Vite development proxy喵。
frontend/plugin-manager/src/views/PluginList.vue Displays pending Auth verification and detailed Market readiness states in the account controls喵。
plugin/tests/integration/test_market_bridge_e2e.py Adds extensive coverage for catalog restrictions, safe diagnostics, identity validation, and OAuth concurrency paths喵。

Sequence Diagram

sequenceDiagram
  participant UI as Plugin Manager
  participant Local as Local Market Bridge
  participant Auth as Auth Service
  participant Market as Plugin Market
  UI->>Local: GET /market/status
  UI->>Local: GET /market/catalog/api/v1/plugins
  Local->>Market: GET /api/v1/plugins
  Market-->>Local: Catalog response
  Local-->>UI: Same-origin catalog response
  UI->>Local: POST /market/oauth/complete
  Local->>Auth: Exchange code and verify userinfo
  Auth-->>Local: OIDC identity
  Local->>Market: Probe account readiness
  Market-->>Local: ready / conflict / unavailable
  Local-->>UI: Independent Auth and Market states
Loading

Reviews (1): Last reviewed commit: "fix(market): harden auth and catalog int..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e5f675c87

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

baseURL: `${baseUrl}/api/v1`,
// Keep catalog traffic same-origin. The local bridge forwards only the
// public, read-only catalog routes and avoids browser CORS failures.
baseURL: '/market/catalog/api/v1',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the catalog bridge to the Vite proxy

When the plugin manager is run via npm run dev, this new relative base URL is served by Vite first, but the dev proxy in frontend/plugin-manager/vite.config.ts only forwards /market/status, auth, install, installed, token-exchange, and tasks routes; /market/catalog/... is not matched. As a result catalog requests hit the Vite dev server instead of the FastAPI bridge and the Market panel cannot load plugins in local development, even though production same-origin hosting works.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
plugin/server/routes/market_bridge.py (1)

1973-2014: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_fetch_auth_userinfo 自己手搓了一套分类与日志,跟新加的 helper 撞车了喵。

分类三元表达式其实就是 _auth_oauth_http_failure_category(只差 400 的处理),日志行也和 _log_auth_oauth_failure 格式重复;而且 res.status_code != 200 判断写了两遍。收敛到 helper 能保证 operation= 字段与其它 Auth 日志一致,后续加字段也只改一处呐。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/routes/market_bridge.py` around lines 1973 - 2014, Update
_fetch_auth_userinfo to use _auth_oauth_http_failure_category for non-200
response classification and _log_auth_oauth_failure for the warning, ensuring
the log includes the helper’s standard operation= field. Remove the duplicated
status checks while preserving 401/403 rejection and returning None for other
non-200 responses; keep invalid JSON handling aligned with the shared helper
where applicable.
frontend/plugin-manager/src/api/market.ts (1)

247-253: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

catalog 已经走同源桥接了,却还被远端 market_url 卡着门呐喵~

baseURL 现在固定是本地桥接路径,但 getClient() 依旧在 getMarketBaseUrl() 返回 null 时直接放弃。于是 /market/status 超时(3s)或没有 market_url 时,目录请求根本不会发出,后端 market_catalog_not_configured / market_catalog_unavailable 的结构化错误也就永远看不到啦。建议只用它预热 web URL 缓存,不要拿它当 catalog 的开关喵。

♻️ 建议改动
-  const configuredMarketUrl = await getMarketBaseUrl()
-  if (!configuredMarketUrl) return null
-
+  // 只用于预热 market_web_url 缓存;catalog 走同源桥接,可用性由后端判定
+  await getMarketBaseUrl()
+
   _marketClient = axios.create({
     // Keep catalog traffic same-origin. The local bridge forwards only the
     // public, read-only catalog routes and avoids browser CORS failures.
     baseURL: '/market/catalog/api/v1',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/plugin-manager/src/api/market.ts` around lines 247 - 253, Update
getClient so a null result from getMarketBaseUrl does not prevent creating the
catalog client; use getMarketBaseUrl only to warm the web URL cache, while
preserving the fixed same-origin baseURL and allowing catalog requests to reach
the bridge and return structured backend errors.
frontend/plugin-manager/src/views/PluginList.vue (1)

54-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

这个按钮点了什么都不会发生,对键盘用户不太友好喵。

它没有 @click,只是个状态指示器,却仍然可聚焦、可点击。建议加 disabled(或直接换成 el-tag/文本 + spinner)并补 aria-live,别让人家白按一场啦~

♻️ 小改法
               <el-button
                 v-if="marketUrl && marketAuth.auth_state === 'pending'"
                 class="market-auth-trigger"
                 :loading="true"
+                disabled
+                aria-live="polite"
                 plain
               >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/plugin-manager/src/views/PluginList.vue` around lines 54 - 62,
Update the pending market-auth button in PluginList.vue so it is non-interactive
by adding the appropriate disabled state, preventing keyboard users from
activating a control with no click behavior. Add an aria-live announcement for
the pending verification status while preserving the existing loading indicator
and label.
frontend/plugin-manager/src/composables/useMarketAuth.ts (1)

205-216: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

未知 market_state 会让 t(undefined) 上桌,建议加个兜底文案喵。

marketAuth.value 直接由 res.json() 原样赋值,后端将来新增一个 MarketOAuthState 值时,marketAuthStateMessageKeys[state]undefined,这里就会 t(undefined)。第 75-81 行的 computed 同理(返回 undefined,模板刚好被 v-else-if 挡住,算运气好)。

🛡️ 兜底建议
-    ElMessage.warning(t(marketAuthStateMessageKeys[state]))
+    const key = marketAuthStateMessageKeys[state]
+    ElMessage.warning(key ? t(key) : t('market.loginFailed'))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/plugin-manager/src/composables/useMarketAuth.ts` around lines 205 -
216, 在 notifyMarketAuthState 中处理未知的 market_state:仅当 state 存在且能在
marketAuthStateMessageKeys 中找到对应文案时调用 t,否则使用明确的默认提示文案。同步更新第 75-81 行附近的 computed
逻辑,确保未知状态返回有效的兜底文案而不是 undefined。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/plugin-manager/src/composables/useMarketAuth.ts`:
- Around line 249-259: Update the readiness retry flow around
scheduleMarketReadinessRetry and the market_state === 'ready' branch so
ElMessage.success(t('market.loginSuccess')) is shown only for retries initiated
by the login flow. Propagate a notifyOnReady indicator through the retry chain,
leaving automatic recovery from initial status loading silent while preserving
account-summary loading and authentication checks.
- Around line 142-168: Update loadMarketAuthStatus and its
scheduleMarketReadinessRetry flow to cap readiness retries and apply exponential
backoff instead of retrying every five seconds indefinitely. Track and reset the
retry state on a successful status load, preserve generation checks, and stop
scheduling once the maximum retry count is reached, including for
unauthenticated users and network/5xx failures.

In `@plugin/server/routes/market_bridge.py`:
- Around line 907-908: Update the locale selection logic near accept_language to
parse Accept-Language entries, honor their q weights and ordering, select the
highest-priority supported language, and then map its primary tag to zh-CN, ja,
or en instead of using substring checks.
- Around line 1004-1033: Update the successful subject branch in the OAuth
status flow to persist subject, subject_pending, and auth_state through
_write_oauth_token_if_matches immediately, before calling
_fresh_cached_market_user. Refresh token_snapshot/token_data from the successful
CAS result for subsequent writes, and preserve the existing conflict response
behavior if persistence fails.

---

Nitpick comments:
In `@frontend/plugin-manager/src/api/market.ts`:
- Around line 247-253: Update getClient so a null result from getMarketBaseUrl
does not prevent creating the catalog client; use getMarketBaseUrl only to warm
the web URL cache, while preserving the fixed same-origin baseURL and allowing
catalog requests to reach the bridge and return structured backend errors.

In `@frontend/plugin-manager/src/composables/useMarketAuth.ts`:
- Around line 205-216: 在 notifyMarketAuthState 中处理未知的 market_state:仅当 state
存在且能在 marketAuthStateMessageKeys 中找到对应文案时调用 t,否则使用明确的默认提示文案。同步更新第 75-81 行附近的
computed 逻辑,确保未知状态返回有效的兜底文案而不是 undefined。

In `@frontend/plugin-manager/src/views/PluginList.vue`:
- Around line 54-62: Update the pending market-auth button in PluginList.vue so
it is non-interactive by adding the appropriate disabled state, preventing
keyboard users from activating a control with no click behavior. Add an
aria-live announcement for the pending verification status while preserving the
existing loading indicator and label.

In `@plugin/server/routes/market_bridge.py`:
- Around line 1973-2014: Update _fetch_auth_userinfo to use
_auth_oauth_http_failure_category for non-200 response classification and
_log_auth_oauth_failure for the warning, ensuring the log includes the helper’s
standard operation= field. Remove the duplicated status checks while preserving
401/403 rejection and returning None for other non-200 responses; keep invalid
JSON handling aligned with the shared helper where applicable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9511bbd5-1466-4a9e-8be3-e8f3e42c0d16

📥 Commits

Reviewing files that changed from the base of the PR and between 366689a and 8e5f675.

📒 Files selected for processing (15)
  • frontend/plugin-manager/src/api/market.test.ts
  • frontend/plugin-manager/src/api/market.ts
  • frontend/plugin-manager/src/composables/useMarketAuth.test.ts
  • frontend/plugin-manager/src/composables/useMarketAuth.ts
  • frontend/plugin-manager/src/i18n/locales/en-US.ts
  • frontend/plugin-manager/src/i18n/locales/es.ts
  • frontend/plugin-manager/src/i18n/locales/ja.ts
  • frontend/plugin-manager/src/i18n/locales/ko.ts
  • frontend/plugin-manager/src/i18n/locales/pt.ts
  • frontend/plugin-manager/src/i18n/locales/ru.ts
  • frontend/plugin-manager/src/i18n/locales/zh-CN.ts
  • frontend/plugin-manager/src/i18n/locales/zh-TW.ts
  • frontend/plugin-manager/src/views/PluginList.vue
  • plugin/server/routes/market_bridge.py
  • plugin/tests/integration/test_market_bridge_e2e.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Project-N-E-K-O/N.E.K.O.-PC (manual)

Comment on lines +142 to 168
async function loadMarketAuthStatus(): Promise<boolean> {
const generation = ++marketAuthStatusGeneration
const token = await ensureBridgeToken({ forceRefresh: true })
if (!token) return
if (!token || generation !== marketAuthStatusGeneration) return false
try {
const res = await authedFetch('/market/oauth/status')
if (!res.ok) return
marketAuth.value = await res.json()
if (!res.ok || generation !== marketAuthStatusGeneration) {
if (res.status >= 500 && generation === marketAuthStatusGeneration) {
scheduleMarketReadinessRetry()
}
return false
}
const status = await res.json()
if (generation !== marketAuthStatusGeneration) return false
marketAuth.value = status
if (status.retryable) {
scheduleMarketReadinessRetry()
}
return true
} catch {
// 登录态只是增强能力,失败不影响 Market 浏览和安装。
if (generation === marketAuthStatusGeneration) {
scheduleMarketReadinessRetry()
}
return false
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

就绪重试没有上限也没有退避,喵,后端挂掉时会永远每 5 秒敲一次门哦~

PluginList.vue 第 769 行在挂载时无条件调用 loadMarketAuthStatus(),所以即使用户从未登录,只要 /market/oauth/status 返回 5xx 或网络异常,就会进入 scheduleMarketReadinessRetry() → 5 秒后再失败 → 再调度的无限循环(每次都还会 forceRefresh 重取 bridge-token,即两个请求)。建议加上最大重试次数与指数退避,笨蛋开发者不要让本喵的电脑一直发热啦。

♻️ 参考改法(次数上限 + 退避)
+  let marketReadinessRetryAttempts = 0
+  const MARKET_READINESS_MAX_RETRIES = 6
   function stopMarketReadinessRetry(): void {
     marketAuthStatusGeneration += 1
+    marketReadinessRetryAttempts = 0
     if (marketReadinessRetryTimer !== null) {
       clearTimeout(marketReadinessRetryTimer)
       marketReadinessRetryTimer = null
     }
   }
 
   function scheduleMarketReadinessRetry(): void {
+    const attempt = marketReadinessRetryAttempts + 1
+    if (attempt > MARKET_READINESS_MAX_RETRIES) return
     stopMarketReadinessRetry()
+    marketReadinessRetryAttempts = attempt
-    marketReadinessRetryTimer = window.setTimeout(async () => {
+    const delay = Math.min(5000 * 2 ** (attempt - 1), 60000)
+    marketReadinessRetryTimer = window.setTimeout(async () => {
       ...
-    }, 5000)
+    }, delay)
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/plugin-manager/src/composables/useMarketAuth.ts` around lines 142 -
168, Update loadMarketAuthStatus and its scheduleMarketReadinessRetry flow to
cap readiness retries and apply exponential backoff instead of retrying every
five seconds indefinitely. Track and reset the retry state on a successful
status load, preserve generation checks, and stop scheduling once the maximum
retry count is reached, including for unauthenticated users and network/5xx
failures.

Comment on lines +249 to +259
marketAuthBusy.value = false
if (!marketAuth.value.authenticated) return
if (marketAuth.value.market_state === 'ready') {
const appliedGeneration = marketAuthStatusGeneration
await loadMarketAccountSummary()
if (
appliedGeneration !== marketAuthStatusGeneration
|| !marketAuth.value.authenticated
) return
ElMessage.success(t('market.loginSuccess'))
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

后台恢复也弹「登录成功」,用户会一脸问号的喵。

这个分支不区分「用户刚点了登录」和「页面挂载时状态请求失败后自动恢复」。测试 useMarketAuth.test.ts 第 216-223 行正是后者却断言 market.loginSuccess——用户什么都没点却收到"授权登录已完成",怪怪的喵。建议只在由登录流程触发的重试链上提示成功(例如给 scheduleMarketReadinessRetry 传一个 notifyOnReady 标记)。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/plugin-manager/src/composables/useMarketAuth.ts` around lines 249 -
259, Update the readiness retry flow around scheduleMarketReadinessRetry and the
market_state === 'ready' branch so ElMessage.success(t('market.loginSuccess'))
is shown only for retries initiated by the login flow. Propagate a notifyOnReady
indicator through the retry chain, leaving automatic recovery from initial
status loading silent while preserving account-summary loading and
authentication checks.

Comment on lines +907 to +908
accept_language = request.headers.get("accept-language", "").lower()
locale = "zh-CN" if "zh" in accept_language else "ja" if "ja" in accept_language else "en"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Accept-Language 用子串匹配会选错语言喵!

"zh" in accept_language 不看权重也不看位置,en-GB,zh-TW;q=0.1 会渲染成中文页面。按 q 值取最优标签再取主语言比较稳妥呐。

🌐 建议改动
-    accept_language = request.headers.get("accept-language", "").lower()
-    locale = "zh-CN" if "zh" in accept_language else "ja" if "ja" in accept_language else "en"
+    accept_language = request.headers.get("accept-language", "").lower()
+    preferred, best_q = "", -1.0
+    for part in accept_language.split(","):
+        tag, _, params = part.strip().partition(";")
+        if not tag:
+            continue
+        quality = 1.0
+        if params.strip().startswith("q="):
+            try:
+                quality = float(params.strip()[2:])
+            except ValueError:
+                quality = 0.0
+        if quality > best_q:
+            preferred, best_q = tag, quality
+    locale = {"zh": "zh-CN", "ja": "ja"}.get(preferred.split("-")[0], "en")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
accept_language = request.headers.get("accept-language", "").lower()
locale = "zh-CN" if "zh" in accept_language else "ja" if "ja" in accept_language else "en"
accept_language = request.headers.get("accept-language", "").lower()
preferred, best_q = "", -1.0
for part in accept_language.split(","):
tag, _, params = part.strip().partition(";")
if not tag:
continue
quality = 1.0
if params.strip().startswith("q="):
try:
quality = float(params.strip()[2:])
except ValueError:
quality = 0.0
if quality > best_q:
preferred, best_q = tag, quality
locale = {"zh": "zh-CN", "ja": "ja"}.get(preferred.split("-")[0], "en")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/routes/market_bridge.py` around lines 907 - 908, Update the
locale selection logic near accept_language to parse Accept-Language entries,
honor their q weights and ordering, select the highest-priority supported
language, and then map its primary tag to zh-CN, ja, or en instead of using
substring checks.

Comment on lines +1004 to 1033
if subject:
token_data["subject"] = subject
token_data["subject_pending"] = False
token_data["auth_state"] = "ready"
else:
token_data["auth_state"] = "pending"
token_data["updated_at"] = time.time()
if not _write_oauth_token_if_matches(token_snapshot, token_data):
return MarketOAuthStatusResponse(
authenticated=False,
market_web_url=MARKET_WEB_URL,
)
return MarketOAuthStatusResponse(
authenticated=False,
auth_state="pending",
retryable=True,
expires_at=token_data.get("expires_at"),
market_web_url=MARKET_WEB_URL,
)

cached_user = _fresh_cached_market_user(token_data)
if cached_user is not None:
return MarketOAuthStatusResponse(
authenticated=True,
auth_state="ready",
market_state="ready",
user=cached_user,
expires_at=token_data.get("expires_at"),
market_web_url=MARKET_WEB_URL,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

刚解析好的 subject 可能没落盘就被 return 掉了喵~

1004-1007 把 subject / subject_pending=False 只写进内存字典;如果紧接着 _fresh_cached_market_user 命中(1024-1033 提前返回),CAS 写回永远不会执行,下一次 /status 又得重新打 userinfo。建议解析成功后立刻 CAS 落盘,并刷新快照给后续写入用呐。

💾 建议改动
         if subject:
             token_data["subject"] = subject
             token_data["subject_pending"] = False
             token_data["auth_state"] = "ready"
+            token_data["updated_at"] = time.time()
+            if not _write_oauth_token_if_matches(token_snapshot, token_data):
+                return MarketOAuthStatusResponse(
+                    authenticated=False,
+                    market_web_url=MARKET_WEB_URL,
+                )
+            token_snapshot = dict(token_data)
         else:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if subject:
token_data["subject"] = subject
token_data["subject_pending"] = False
token_data["auth_state"] = "ready"
else:
token_data["auth_state"] = "pending"
token_data["updated_at"] = time.time()
if not _write_oauth_token_if_matches(token_snapshot, token_data):
return MarketOAuthStatusResponse(
authenticated=False,
market_web_url=MARKET_WEB_URL,
)
return MarketOAuthStatusResponse(
authenticated=False,
auth_state="pending",
retryable=True,
expires_at=token_data.get("expires_at"),
market_web_url=MARKET_WEB_URL,
)
cached_user = _fresh_cached_market_user(token_data)
if cached_user is not None:
return MarketOAuthStatusResponse(
authenticated=True,
auth_state="ready",
market_state="ready",
user=cached_user,
expires_at=token_data.get("expires_at"),
market_web_url=MARKET_WEB_URL,
)
if subject:
token_data["subject"] = subject
token_data["subject_pending"] = False
token_data["auth_state"] = "ready"
token_data["updated_at"] = time.time()
if not _write_oauth_token_if_matches(token_snapshot, token_data):
return MarketOAuthStatusResponse(
authenticated=False,
market_web_url=MARKET_WEB_URL,
)
token_snapshot = dict(token_data)
else:
token_data["auth_state"] = "pending"
token_data["updated_at"] = time.time()
if not _write_oauth_token_if_matches(token_snapshot, token_data):
return MarketOAuthStatusResponse(
authenticated=False,
market_web_url=MARKET_WEB_URL,
)
return MarketOAuthStatusResponse(
authenticated=False,
auth_state="pending",
retryable=True,
expires_at=token_data.get("expires_at"),
market_web_url=MARKET_WEB_URL,
)
cached_user = _fresh_cached_market_user(token_data)
if cached_user is not None:
return MarketOAuthStatusResponse(
authenticated=True,
auth_state="ready",
market_state="ready",
user=cached_user,
expires_at=token_data.get("expires_at"),
market_web_url=MARKET_WEB_URL,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/server/routes/market_bridge.py` around lines 1004 - 1033, Update the
successful subject branch in the OAuth status flow to persist subject,
subject_pending, and auth_state through _write_oauth_token_if_matches
immediately, before calling _fresh_cached_market_user. Refresh
token_snapshot/token_data from the successful CAS result for subsequent writes,
and preserve the existing conflict response behavior if persistence fails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant