Browse Source

arrange for using github access tokens for the repo page

master
Silberengel 4 weeks ago
parent
commit
1042467f9e
  1. 10
      src/lib/components/content/FileExplorer.svelte
  2. 46
      src/lib/services/content/git-repo-fetcher.ts
  3. 17
      src/lib/services/github-api.ts
  4. 7
      src/routes/repos/[naddr]/+page.svelte

10
src/lib/components/content/FileExplorer.svelte

@ -117,9 +117,13 @@ @@ -117,9 +117,13 @@
}
// Use GitHub API helper for GitHub repos (handles rate limiting and token fallback)
const response = url.includes('github.com')
? await fetchGitHubApi(apiUrl)
: await fetch(apiUrl);
let response: Response;
if (url.includes('github.com')) {
const apiResult = await fetchGitHubApi(apiUrl);
response = apiResult.response;
} else {
response = await fetch(apiUrl);
}
if (!response.ok) {
throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`);

46
src/lib/services/content/git-repo-fetcher.ts

@ -18,6 +18,7 @@ export interface GitRepoInfo { @@ -18,6 +18,7 @@ export interface GitRepoInfo {
content: string;
format: 'markdown' | 'asciidoc';
};
usingGitHubToken?: boolean; // Indicates if GitHub API token was used
}
export interface GitBranch {
@ -89,19 +90,29 @@ function parseGitUrl(url: string): { platform: string; owner: string; repo: stri @@ -89,19 +90,29 @@ function parseGitUrl(url: string): { platform: string; owner: string; repo: stri
*/
async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo | null> {
try {
const repoResponse = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}`);
if (!repoResponse.ok) {
console.warn(`GitHub API error for repo ${owner}/${repo}: ${repoResponse.status} ${repoResponse.statusText}`);
const repoApiResult = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}`);
if (!repoApiResult.response.ok) {
console.warn(`GitHub API error for repo ${owner}/${repo}: ${repoApiResult.response.status} ${repoApiResult.response.statusText}`);
return null;
}
const repoData = await repoResponse.json();
const repoData = await repoApiResult.response.json();
// Track if any request used a token
let usingToken = repoApiResult.usedToken;
const defaultBranch = repoData.default_branch || 'main';
const [branchesResponse, commitsResponse, treeResponse] = await Promise.all([
const [branchesResult, commitsResult, treeResult] = await Promise.all([
fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/branches`),
fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=10`),
fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/git/trees/${defaultBranch}?recursive=1`).catch(() => null)
fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/git/trees/${defaultBranch}?recursive=1`).catch(() => ({ response: null as any, usedToken: false }))
]);
// Update token usage flag if any request used a token
usingToken = usingToken || branchesResult.usedToken || commitsResult.usedToken || (treeResult?.usedToken ?? false);
const branchesResponse = branchesResult.response;
const commitsResponse = commitsResult.response;
const treeResponse = treeResult?.response;
// Check if responses are OK and parse JSON
let branchesData: any[] = [];
@ -206,10 +217,12 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo @@ -206,10 +217,12 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo
const readmeFiles = ['README.adoc', 'README.md', 'README.rst', 'README.txt'];
for (const readmeFile of readmeFiles) {
try {
const readmeData = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmeFile}?ref=${defaultBranch}`).then(r => {
if (!r.ok) throw new Error('Not found');
return r.json();
});
const readmeResult = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmeFile}?ref=${defaultBranch}`);
if (readmeResult.usedToken) {
usingToken = true;
}
if (!readmeResult.response.ok) throw new Error('Not found');
const readmeData = await readmeResult.response.json();
if (readmeData.content) {
const content = atob(readmeData.content.replace(/\s/g, ''));
readme = {
@ -244,10 +257,12 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo @@ -244,10 +257,12 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo
// If found in tree, fetch it
if (readmePath) {
try {
const readmeData = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmePath}?ref=${defaultBranch}`).then(r => {
if (!r.ok) throw new Error('Not found');
return r.json();
});
const readmeResult = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmePath}?ref=${defaultBranch}`);
if (readmeResult.usedToken) {
usingToken = true;
}
if (!readmeResult.response.ok) throw new Error('Not found');
const readmeData = await readmeResult.response.json();
if (readmeData.content) {
const content = atob(readmeData.content.replace(/\s/g, ''));
const format = readmePath.toLowerCase().endsWith('.adoc') ? 'asciidoc' : 'markdown';
@ -271,7 +286,8 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo @@ -271,7 +286,8 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo
branches,
commits,
files,
readme
readme,
usingGitHubToken: usingToken
};
} catch (error) {
console.error('Error fetching from GitHub:', error);

17
src/lib/services/github-api.ts

@ -73,16 +73,24 @@ async function getGitHubToken(): Promise<string | null> { @@ -73,16 +73,24 @@ async function getGitHubToken(): Promise<string | null> {
}
}
/**
* Result of a GitHub API request, including whether a token was used
*/
export interface GitHubApiResponse {
response: Response;
usedToken: boolean;
}
/**
* Make a GitHub API request with automatic token fallback on rate limiting
* @param url - GitHub API URL
* @param options - Fetch options (headers will be merged)
* @returns Response object
* @returns Response object and whether a token was used
*/
export async function fetchGitHubApi(
url: string,
options: RequestInit = {}
): Promise<Response> {
): Promise<GitHubApiResponse> {
// First attempt without token
let response = await fetch(url, {
...options,
@ -92,6 +100,8 @@ export async function fetchGitHubApi( @@ -92,6 +100,8 @@ export async function fetchGitHubApi(
}
});
let usedToken = false;
// Check if rate limited
if (isRateLimited(response)) {
console.log('GitHub API rate limit detected, attempting to use saved token...');
@ -101,6 +111,7 @@ export async function fetchGitHubApi( @@ -101,6 +111,7 @@ export async function fetchGitHubApi(
if (token) {
console.log('Using saved GitHub token for authenticated request');
usedToken = true;
// Retry with token (use Bearer for compatibility with both classic and fine-grained tokens)
response = await fetch(url, {
@ -133,5 +144,5 @@ export async function fetchGitHubApi( @@ -133,5 +144,5 @@ export async function fetchGitHubApi(
}
}
return response;
return { response, usedToken };
}

7
src/routes/repos/[naddr]/+page.svelte

@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
import { cacheEvent, getEventsByKind } from '../../../lib/services/cache/event-cache.js';
import EventMenu from '../../../lib/components/EventMenu.svelte';
import { fetchProfiles } from '../../../lib/services/user-data.js';
import Icon from '../../../lib/components/ui/Icon.svelte';
let naddr = $derived($page.params.naddr);
let repoEvent = $state<NostrEvent | null>(null);
@ -895,6 +896,12 @@ @@ -895,6 +896,12 @@
<EventMenu event={repoEvent} showContentActions={true} />
{/if}
</div>
{#if gitRepo?.usingGitHubToken}
<div class="github-token-notice mb-4 p-3 bg-fog-highlight dark:bg-fog-dark-highlight border border-fog-border dark:border-fog-dark-border rounded text-sm text-fog-text dark:text-fog-dark-text">
<Icon name="key" size={16} class="inline mr-2" />
Using your saved GitHub API token for authenticated requests
</div>
{/if}
{#if getRepoDescription()}
<p class="text-fog-text-light dark:text-fog-dark-text-light mb-4">
{getRepoDescription()}

Loading…
Cancel
Save