Homepage 集成 Kavita 小部件:配置、鉴权与会话令牌管理实战
【免费下载链接】homepageA highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.项目地址: https://gitcode.com/GitHub_Trending/ho/homepage
本指南介绍如何在 Homepage 项目(项目根目录)的services.yaml中接入自托管的 Kavita 电子书服务器,通过官方小部件实时展示「系列数量(Series)」与「文件数量(Files)」两项统计。读完本文,你将掌握 Kavita 小部件的完整配置语法、用户名密码与 API Key 两种鉴权方式的取舍,以及其底层代理(proxy)如何完成登录、令牌缓存与 401 自动重试的完整链路。
Kavita 小部件是什么
Kavita 是开源的电子书/漫画阅读服务器,Homepage 为其提供了开箱即用的服务型小部件(Service Widget)。小部件通过 Kavita 的 REST API 读取服务器统计信息,并在首页仪表盘上以两个数据块(Block)展示:
- Series(系列数):Kavita 服务器中漫画/书籍系列的数量;
- Files(文件数):Kavita 服务器中索引的文件总量。
从源码结构看,该小部件由三部分组成(位于 src/widgets/kavita/):
| 文件 | 职责 |
|---|---|
| widget.js | 声明小部件的 API 模板与数据映射(mappings) |
| proxy.js | 后端代理,负责登录鉴权、会话令牌缓存与统计接口调用 |
| component.jsx | 前端渲染组件,负责数据展示与错误/加载状态 |
该小部件在 src/widgets/widgets.js 中注册(import kavita from "./kavita/widget"),并已在服务小部件索引 docs/widgets/services/index.md 中列出,因此只需在配置中声明type: kavita即可被 Homepage 识别。
配置示例与参数说明
Kavita 小部件使用与 Web 登录相同的管理员角色账号密码。在services.yaml中添加:
widget: type: kavita url: http://kavita.host.or.ip:port username: username password: password key: kavitaapikey # 可选,例如不使用用户名密码时参数详解
| 参数 | 必填 | 说明 |
|---|---|---|
type | 是 | 固定为kavita,用于在 widgets.js 中定位小部件定义 |
url | 是 | Kavita 服务地址,格式为http(s)://host:port,需与 Homepage 后端网络可达 |
username/password | 二选一 | 管理员角色账号密码,调用Account/login接口换取 JWT 会话令牌 |
key | 二选一 | Kavita API Key,用于跳过账号密码直接鉴权 |
鉴权方式选择:username+password与key二者提供其一即可。若同时提供,从 proxy.js 的实现来看,账号密码优先(先判断widget.username && widget.password,再判断widget.key)。官方文档建议:如果不想使用账号密码,可直接配置 API Key(key字段)。
允许字段(Allowed fields)
官方文档明确允许的字段为["seriesCount", "totalFiles"],即代理最终只向客户端返回这两项数据。对应地,component.jsx 通过kavitaData.seriesCount与kavitaData.totalFiles渲染两个Block,翻译键kavita.seriesCount("Series")与kavita.totalFiles("Files")定义在 public/locales/en/common.json。
底层实现:登录、令牌缓存与 401 自动重试
1. API 模板与数据映射
widget.js 中定义:
const widget = { api: "{url}/api/{endpoint}", proxyHandler: kavitaProxyHandler, mappings: { info: { endpoint: "/", }, }, };api是 URL 模板,{url}由配置中的url替换,{endpoint}由各调用点传入;info映射声明该小部件有一个名为info的数据端点(endpoint 为/,实际请求时会被具体接口路径覆盖);proxyHandler指向自定义代理kavitaProxyHandler,说明 Kavita 的鉴权流程无法用通用代理处理,需要专用实现。
2. 登录获取会话令牌(login)
当缓存中不存在会话令牌时,代理会先调用 Kavita 的Account/login接口:
const endpoint = "Account/login"; const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget })); const loginBody = { username: "", password: "", apiKey: "" }; if (widget.username && widget.password) { loginBody.username = widget.username; loginBody.password = widget.password; } else if (widget.key) { loginBody.apiKey = widget.key; } const headers = { "Content-Type": "application/json", accept: "text/plain" }; const [, , data] = await httpProxy(loginUrl, { method: "POST", body: JSON.stringify(loginBody), headers, });要点:
- 登录请求为
POST {url}/api/Account/login,请求体为 JSON,内容根据鉴权方式填充username/password或apiKey; - 响应体中包含
token(JWT),随后通过cache.put(${sessionTokenCacheKey}.${service}, accessToken)写入内存缓存; - 缓存键为
kavitaProxyHandler__sessionToken.<service>,以service维度隔离,避免多实例互相覆盖; - 若登录失败(响应中无
token),代理返回{ token: false },并记录错误日志"Unable to login to Kavita API"。
3. 带令牌访问统计接口(apiCall)
登录成功后,代理携带Authorization: Bearer <token>请求统计数据:
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); const headers = { "content-type": "application/json", Authorization: `Bearer ${cache.get(key)}`, }; let [status, contentType, data, responseHeaders] = await httpProxy(url, { method: "GET", headers });最终请求的统计接口为GET {url}/api/Stats/server/stats(见 proxy.js),返回体中的seriesCount与totalFiles会被透传给前端:
return res.status(200).send({ seriesCount: statsData?.seriesCount, totalFiles: statsData?.totalFiles, });4. 401/403 自动重新登录重试
令牌过期是常见问题。代理对 401/403 做了容错:第一次请求被拒绝时,自动重新调用login()获取新令牌,并使用新令牌重试原请求:
if (status === 401 || status === 403) { const { accessToken } = await login(widget, service); headers.Authorization = `Bearer ${accessToken}`; [status, contentType, data, responseHeaders] = await httpProxy(url, { method, headers }); }这一机制保证了即使会话令牌中途失效,小部件也能自愈,无需重启 Homepage。
5. 请求入口与校验
kavitaProxyHandler(proxy.js)接收来自前端的请求,依次执行:
- 校验
group/service查询参数,缺失则返回 400"Invalid proxy service type"; - 通过
getServiceWidget(group, service, index)从用户配置中读取小部件定义,读取失败同样返回 400; - 若缓存中无会话令牌,先调用
login; - 调用
apiCall获取统计,组装{ seriesCount, totalFiles }返回 200。
注意:
service参数会拼入令牌缓存键,因此配置中不同的 service 名对应独立的登录会话,互不影响。
测试用例佐证
仓库为 Kavita 小部件提供了完整的单元测试,可直接验证上述行为:
- proxy.test.js:模拟登录返回
token后请求统计接口,断言httpProxy被调用 2 次(一次登录、一次统计),返回体为{ seriesCount: 5, totalFiles: 100 }; - proxy.test.js:预置旧令牌,模拟首次请求返回 401、重试后成功,断言自动重新登录(
Account/login恰好调用 1 次)且最终返回正确统计——印证了「令牌过期自动续期」设计; - component.test.jsx:断言加载状态下渲染 2 个
.service-block占位块(分别带kavita.seriesCount、kavita.totalFiles标签); - component.test.jsx:断言数据就绪后正确渲染数值
12与34; - widget.test.js:验证小部件配置结构合法。
常见问题排查
- 401 频繁:多为令牌过期或账号密码变更。代理会自动重试一次;若仍失败,请检查
username/password是否为管理员角色账号,或在 Kavita 后台重新生成 API Key 填入key字段。 - 连接失败:确认
url从 Homepage 运行环境(如 Docker 容器)可访问,注意容器间使用服务名而非localhost。 - 只显示占位块不显示数字:加载中状态会显示两个空块;若长时间停留且无报错,查看 Homepage 后端日志中的
kavitaProxyHandler相关错误(如"Unable to login to Kavita API"或"Error getting data from Kavita")。 - 字段选择:当前版本仅支持
seriesCount与totalFiles两个字段,无需也无法额外配置显示字段。
小结
Kavita 小部件虽然配置简单(一份 YAML 即可接入),但底层包含完整的「登录 → 缓存令牌 → 携带令牌请求统计 → 401 自动重试」闭环。理解 proxy.js 的实现,不仅有助于排查鉴权类问题,也为阅读 Homepage 其他带鉴权的服务小部件(如 Jellyfin、Komga 等)提供了通用方法论。更多服务小部件的配置方式可参考 docs/widgets/services/index.md。
【免费下载链接】homepageA highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.项目地址: https://gitcode.com/GitHub_Trending/ho/homepage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考