GitZip Pro 源码解析:一个 GitHub 文件/文件夹下载扩展是如何工作的(三)URL 解析与 GitHub API 请求
项目地址:fthux/GitZipPro。本文继续从源码角度拆解 GitZip Pro 的下载器实现。
上一篇分析了 content.js 如何进入 GitHub 页面、扫描文件列表并维护选中项。页面层最终会把一个 selectedItems 传给下载器。这个 selectedItems 里保存的是 GitHub 文件或目录链接。
从这一篇开始,源码主角变成 source/downloader.js。
downloader.js 的任务可以分成两半:前半段负责理解“用户选中了什么”,也就是 URL 解析和 GitHub API 请求;后半段负责“把这些内容下载下来并打包”,也就是递归收集、并发下载和 zip 生成。本篇先讲前半段。
下载器的输入
在 content.js 中,选中项保存在一个 Map 里:
selectedItems.set(row, getPathFromRow(row));
也就是说,下载器拿到的不是一个已经结构化的对象,而是一组 GitHub href,例如:
/fthux/GitZipPro/tree/main/source
/fthux/GitZipPro/blob/main/README.md
下载器第一步要做的事情,就是把这些路径解析成后续 API 请求需要的结构:
{
owner,
repo,
branch,
path,
type: 'file' | 'dir'
}
对应函数就是 parseGitHubUrl()。
parseGitHubUrl:从 GitHub 链接中提取下载目标
parseGitHubUrl(href) 首先会把传入的 href 标准化成 URL:
url = new URL(href, 'https://github.com');
这里第二个参数很关键。因为从 GitHub 页面 DOM 中拿到的 href 可能是完整 URL,也可能只是相对路径。使用 new URL(href, 'https://github.com') 可以统一处理这两种情况。
接着函数会限制 host:
if (url.hostname !== 'github.com') return null;
这保证下载器只处理 GitHub 链接。
然后把 pathname 拆成数组:
const parts = url.pathname.replace(/^\//, '').split('/').filter(Boolean);
典型文件路径会变成:
fthux/GitZipPro/blob/main/README.md
拆开后:
parts[0] = owner
parts[1] = repo
parts[2] = blob 或 tree
parts[3] = branch
parts[4...] = path
如果第三段是 tree,说明这是目录;如果第三段是 blob,说明这是文件。代码最终返回:
return { owner, repo, branch, path, type: 'dir' };
或:
return { owner, repo, branch, path, type: 'file' };
这个函数是下载器的入口转换器。它把 GitHub 页面链接转换成 GitHub API 可以理解的参数。
下载前的设置读取
下载入口 start(selectedItems, callbacks) 一开始会调用 getSettings()。这个函数从 chrome.storage.local 里读取下载相关设置:
chrome.storage.local.get(
[
STORAGE.NAMING_PRESET,
STORAGE.NAMING_CUSTOM,
STORAGE.NOTIFY_SHOW,
STORAGE.NOTIFY_SOUND,
STORAGE.NOTIFY_OPEN,
STORAGE.IGNORE_LABELS,
STORAGE.IGNORE_CUSTOM_VARS,
STORAGE.GITHUB_TOKEN,
STORAGE.TOKEN_ACCESS_MODE
],
...
)
这里读到的设置会影响后面的流程:
- 命名规则决定 zip 文件名。
- 通知设置决定下载完成后是否提醒。
- 忽略规则决定哪些文件不进入 zip。
- GitHub token 和访问模式决定 API 请求头。
也就是说,downloader.js 不是一个纯粹的网络请求模块,它会把用户配置纳入下载流程。
buildHeaders:匿名模式与 token 模式
GitHub API 可以匿名访问,但匿名访问有更严格的 rate limit。GitZip Pro 支持用户配置 token,所以请求头构造被封装成了 buildHeaders():
function buildHeaders(githubToken = '', tokenAccessMode = 'anonymous') {
const headers = {
'Accept': 'application/vnd.github.v3+json'
};
if (githubToken && tokenAccessMode === 'custom') {
headers['Authorization'] = `token ${githubToken}`;
}
return headers;
}
这里的逻辑很明确:
- 所有请求都带
Accept: application/vnd.github.v3+json。 - 只有在
custom模式且 token 存在时,才添加Authorization。 - 匿名模式不会把 token 放进请求头。
这让下载器可以统一调用 GitHub API,而不必在每个请求处重复判断 token。
githubFetch:统一 API 请求与重试
真正发起 GitHub API 请求的是 githubFetch(apiPath, signal, githubToken, tokenAccessMode, attempt)。
它把相对 API 路径拼到 GitHub API base URL 上:
const fullUrl = `${GITHUB_API_BASE}${apiPath}`;
然后调用 fetch:
resp = await fetch(fullUrl, { headers, signal });
这里的 signal 来自 AbortController,用于取消下载任务。比如用户取消下载时,后续请求可以被中断。
githubFetch 还处理了限流重试。如果返回状态是 429,或者是带有限流语义的 403,并且重试次数还没超过 3 次,就会等待一段时间后再次请求:
const retryAfter = resp.headers.get('Retry-After');
const wait = retryAfter ? parseInt(retryAfter) * 1000 : (attempt + 1) * 2000;
这个封装让上层函数不需要关心每次请求的错误处理细节。它们只要调用 githubFetch(),拿到 JSON 数据即可。
listDir:请求目录内容
目录列表由 listDir() 完成:
const apiPath = `/repos/${owner}/${repo}/contents/${encodeURIFilePath(path)}?ref=${branch}`;
const data = await githubFetch(apiPath, signal, githubToken, tokenAccessMode);
GitHub Contents API 对目录返回数组,对文件返回对象。因此 listDir() 会检查返回值必须是数组:
if (!Array.isArray(data)) {
throw new Error(`Expected directory listing for: ${path}`);
}
这说明 listDir() 只负责目录场景。文件内容的读取会交给 fetchFile()。
fetchFile:请求文件内容
fetchFile() 根据 owner、repo、branch、path 获取单个文件内容。它同样依赖 GitHub Contents API,但文件响应中会包含编码后的内容。下载器后续会把内容转换成二进制数据,放入 zip。
本篇不展开 base64 转换和 symlink 处理,先把主线看清楚:
这个流程是下载器前半段的核心。
URL 解析与 API 请求的分工
这一篇看到的几个函数边界非常清楚:
parseGitHubUrl()只负责把 GitHub 页面 URL 解析成结构化信息。buildHeaders()只负责根据 token 设置构造请求头。githubFetch()只负责统一请求、错误处理和限流重试。listDir()只负责目录列表。fetchFile()只负责单文件内容。
这些函数组合起来,完成了从“页面链接”到“GitHub API 数据”的转换。
本篇小结
downloader.js 的前半段解决的是下载前的识别问题。它从 content script 传来的 href 出发,解析出仓库、分支、路径和类型,再通过 GitHub API 获取文件或目录数据。
下一篇继续沿着 downloader.js 往后走:当目录数据拿到之后,GitZip Pro 如何递归收集所有文件、控制并发下载、写入 JSZip,并最终把 zip 交给 background 触发浏览器下载。
你可以在 fthux/GitZipPro 查看完整项目源码,也欢迎 Star 支持这个 GitHub 文件/文件夹下载扩展。
如果你也好奇这个 Pro 版为什么会做起来,可以接着看这篇项目缘起:用了 GitZip 这么多年,我动手做了一个「Pro」版。
更多推荐


所有评论(0)