Skip to content

Commit eb3a02c

Browse files
committed
feat: improve PAT integration and error handling for v1.1.0-rc
- Profile scanner now uses authenticated API (githubFetch) - Distinguish between rate limit, API error, and no repos - Inline PAT input on rate limit error with stacked layout - Context-specific empty state messages - Rate limit UI matches popup's dark minimal style - Added [HELP] section with DM link
1 parent 108d875 commit eb3a02c

3 files changed

Lines changed: 243 additions & 73 deletions

File tree

utils/api/github.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async function getAuthHeaders(): Promise<HeadersInit> {
8282
/**
8383
* Authenticated fetch wrapper for GitHub API
8484
*/
85-
async function githubFetch(url: string): Promise<Response> {
85+
export async function githubFetch(url: string): Promise<Response> {
8686
const headers = await getAuthHeaders();
8787
return fetch(url, { headers });
8888
}
@@ -169,13 +169,21 @@ export async function fetchRepoTree(owner: string, repo: string): Promise<string
169169
}
170170
}
171171

172+
export interface FetchReposResult {
173+
repos: RepoInfo[];
174+
rateLimited: boolean;
175+
error: boolean;
176+
}
177+
172178
/**
173179
* Fetch user's public repositories
174180
*/
175-
export async function fetchUserRepos(username: string): Promise<RepoInfo[]> {
181+
export async function fetchUserRepos(username: string): Promise<FetchReposResult> {
176182
const repos: RepoInfo[] = [];
177183
let page = 1;
178184
const maxPages = 3;
185+
let rateLimited = false;
186+
let error = false;
179187

180188
try {
181189
while (page <= maxPages) {
@@ -186,8 +194,13 @@ export async function fetchUserRepos(username: string): Promise<RepoInfo[]> {
186194

187195
if (!res.ok) {
188196
if (res.status === 403) {
189-
console.warn('[GitStack] Rate limited on user repos!');
197+
const remaining = res.headers.get('X-RateLimit-Remaining');
198+
if (remaining === '0') {
199+
console.warn('[GitStack] Rate limited on user repos!');
200+
rateLimited = true;
201+
}
190202
}
203+
error = true;
191204
break;
192205
}
193206
const data = await res.json();
@@ -198,9 +211,10 @@ export async function fetchUserRepos(username: string): Promise<RepoInfo[]> {
198211
}
199212
} catch (e) {
200213
console.warn('[GitStack] Error fetching repos:', e);
214+
error = true;
201215
}
202216

203-
return repos;
217+
return { repos, rateLimited, error: error && repos.length === 0 };
204218
}
205219

206220
/**

utils/profile/scanner.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import { signatures } from '../signatures';
6-
import { fetchUserRepos, RepoInfo } from '../api/github';
6+
import { fetchUserRepos, RepoInfo, githubFetch } from '../api/github';
77
import { CACHE_TTL, getCacheWithTimestamp, setCache } from '../cache';
88
import {
99
injectProfileLoadingState,
@@ -94,7 +94,7 @@ async function scanRepoQuick(repo: RepoInfo): Promise<ScanResult> {
9494
}
9595

9696
try {
97-
const treeRes = await fetch(
97+
const treeRes = await githubFetch(
9898
`https://api.github.com/repos/${repo.full_name}/git/trees/${repo.default_branch}?recursive=1`
9999
);
100100

@@ -151,10 +151,22 @@ export async function scanAndDisplayProfile(username: string): Promise<void> {
151151
injectProfileLoadingState(username);
152152
scannedProfileUsernames.add(username);
153153

154-
const repos = await fetchUserRepos(username);
154+
const { repos, rateLimited, error } = await fetchUserRepos(username);
155+
156+
// Handle rate limit
157+
if (rateLimited) {
158+
injectProfileRateLimitError(username);
159+
return;
160+
}
161+
162+
// Handle API error vs genuinely no repos
155163
if (repos.length === 0) {
156164
removeProfileLoadingState();
157-
injectProfileEmptyState(username, 0);
165+
if (error) {
166+
injectProfileEmptyState(username, 0, 'api_error');
167+
} else {
168+
injectProfileEmptyState(username, 0, 'no_repos');
169+
}
158170
return;
159171
}
160172

@@ -216,12 +228,12 @@ export async function scanAndDisplayProfile(username: string): Promise<void> {
216228
});
217229

218230
if (newTechs.size === 0) {
219-
injectProfileEmptyState(username, scannedCount);
231+
injectProfileEmptyState(username, scannedCount, 'scanned_nothing');
220232
}
221233
} else if (cachedTechs.length === 0) {
222234
removeProfileLoadingState();
223235
if (repos.length > 0) {
224-
injectProfileEmptyState(username, 0);
236+
injectProfileEmptyState(username, 0, 'scanned_nothing');
225237
}
226238
}
227239
}

0 commit comments

Comments
 (0)