@@ -59,13 +59,15 @@ async function checkGitHubActivity() {
5959 const newActivities = [ ] ;
6060
6161 for ( const repo of watchedRepos ) {
62- const activities = await fetchRepoActivity ( repo , githubToken , lastCheckDate , enabledFilters ) ;
62+ // Handle both string format (legacy) and object format (new)
63+ const repoName = typeof repo === 'string' ? repo : repo . fullName ;
64+ const activities = await fetchRepoActivity ( repoName , githubToken , lastCheckDate , enabledFilters ) ;
6365 newActivities . push ( ...activities ) ;
6466 }
6567
6668 if ( newActivities . length > 0 ) {
6769 await storeActivities ( newActivities ) ;
68- updateBadge ( newActivities . length ) ;
70+ await updateBadge ( ) ;
6971 showNotifications ( newActivities ) ;
7072 }
7173
@@ -82,84 +84,134 @@ async function fetchRepoActivity(repo, token, since, filters) {
8284 'Accept' : 'application/vnd.github.v3+json'
8385 } ;
8486
87+ async function fetchWithRateLimit ( url ) {
88+ const response = await fetch ( url , { headers } ) ;
89+
90+ // Track rate limits
91+ const remaining = response . headers . get ( 'X-RateLimit-Remaining' ) ;
92+ const limit = response . headers . get ( 'X-RateLimit-Limit' ) ;
93+ const reset = response . headers . get ( 'X-RateLimit-Reset' ) ;
94+
95+ if ( remaining && limit ) {
96+ await chrome . storage . local . set ( {
97+ rateLimit : {
98+ remaining : parseInt ( remaining ) ,
99+ limit : parseInt ( limit ) ,
100+ reset : parseInt ( reset ) * 1000
101+ }
102+ } ) ;
103+ }
104+
105+ if ( ! response . ok ) {
106+ if ( response . status === 401 ) {
107+ throw new Error ( 'Invalid GitHub token' ) ;
108+ } else if ( response . status === 403 ) {
109+ throw new Error ( 'Rate limit exceeded' ) ;
110+ } else if ( response . status === 404 ) {
111+ throw new Error ( `Repository ${ repo } not found` ) ;
112+ } else {
113+ throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` ) ;
114+ }
115+ }
116+
117+ return response ;
118+ }
119+
85120 try {
86121 // Fetch PRs
87122 if ( filters . prs ) {
88- const prsResponse = await fetch (
89- `https://api.github.com/repos/${ repo } /pulls?state=open&sort=created&direction=desc` ,
90- { headers }
123+ const prsResponse = await fetchWithRateLimit (
124+ `https://api.github.com/repos/${ repo } /pulls?state=open&sort=created&direction=desc`
91125 ) ;
92126
93- if ( prsResponse . ok ) {
94- const prs = await prsResponse . json ( ) ;
95- const newPrs = prs . filter ( pr => new Date ( pr . created_at ) > since ) ;
96- activities . push ( ...newPrs . map ( pr => ( {
97- type : 'pr' ,
98- repo,
99- title : pr . title ,
100- url : pr . html_url ,
101- createdAt : pr . created_at ,
102- author : pr . user . login
103- } ) ) ) ;
104- }
127+ const prs = await prsResponse . json ( ) ;
128+ const newPrs = prs . filter ( pr => new Date ( pr . created_at ) > since ) ;
129+ activities . push ( ...newPrs . map ( pr => ( {
130+ id : `pr-${ repo } -${ pr . number } ` ,
131+ type : 'pr' ,
132+ repo,
133+ title : pr . title ,
134+ url : pr . html_url ,
135+ createdAt : pr . created_at ,
136+ author : pr . user . login ,
137+ authorAvatar : pr . user . avatar_url ,
138+ number : pr . number
139+ } ) ) ) ;
105140 }
106141
107142 // Fetch Issues
108143 if ( filters . issues ) {
109- const issuesResponse = await fetch (
110- `https://api.github.com/repos/${ repo } /issues?state=open&sort=created&direction=desc` ,
111- { headers }
144+ const issuesResponse = await fetchWithRateLimit (
145+ `https://api.github.com/repos/${ repo } /issues?state=open&sort=created&direction=desc`
112146 ) ;
113147
114- if ( issuesResponse . ok ) {
115- const issues = await issuesResponse . json ( ) ;
116- const newIssues = issues . filter ( issue => ! issue . pull_request && new Date ( issue . created_at ) > since ) ;
117- activities . push ( ...newIssues . map ( issue => ( {
118- type : 'issue' ,
119- repo,
120- title : issue . title ,
121- url : issue . html_url ,
122- createdAt : issue . created_at ,
123- author : issue . user . login
124- } ) ) ) ;
125- }
148+ const issues = await issuesResponse . json ( ) ;
149+ const newIssues = issues . filter ( issue => ! issue . pull_request && new Date ( issue . created_at ) > since ) ;
150+ activities . push ( ...newIssues . map ( issue => ( {
151+ id : `issue-${ repo } -${ issue . number } ` ,
152+ type : 'issue' ,
153+ repo,
154+ title : issue . title ,
155+ url : issue . html_url ,
156+ createdAt : issue . created_at ,
157+ author : issue . user . login ,
158+ authorAvatar : issue . user . avatar_url ,
159+ number : issue . number
160+ } ) ) ) ;
126161 }
127162
128163 // Fetch Releases
129164 if ( filters . releases ) {
130- const releasesResponse = await fetch (
131- `https://api.github.com/repos/${ repo } /releases` ,
132- { headers }
165+ const releasesResponse = await fetchWithRateLimit (
166+ `https://api.github.com/repos/${ repo } /releases`
133167 ) ;
134168
135- if ( releasesResponse . ok ) {
136- const releases = await releasesResponse . json ( ) ;
137- const newReleases = releases . filter ( release => new Date ( release . published_at ) > since ) ;
138- activities . push ( ... newReleases . map ( release => ( {
139- type : 'release' ,
140- repo,
141- title : release . name || release . tag_name ,
142- url : release . html_url ,
143- createdAt : release . published_at ,
144- author : release . author . login
145- } ) ) ) ;
146- }
169+ const releases = await releasesResponse . json ( ) ;
170+ const newReleases = releases . filter ( release => new Date ( release . published_at ) > since ) ;
171+ activities . push ( ... newReleases . map ( release => ( {
172+ id : `release- ${ repo } - ${ release . id } ` ,
173+ type : 'release' ,
174+ repo,
175+ title : release . name || release . tag_name ,
176+ url : release . html_url ,
177+ createdAt : release . published_at ,
178+ author : release . author . login ,
179+ authorAvatar : release . author . avatar_url
180+ } ) ) ) ;
147181 }
148182 } catch ( error ) {
149- console . error ( `Error fetching activity for ${ repo } :` , error ) ;
183+ console . error ( `Error fetching activity for ${ repo } :` , error . message ) ;
184+ await chrome . storage . local . set ( {
185+ lastError : {
186+ message : error . message ,
187+ repo,
188+ timestamp : Date . now ( )
189+ }
190+ } ) ;
191+ throw error ;
150192 }
151193
152194 return activities ;
153195}
154196
155197async function storeActivities ( newActivities ) {
156198 const { activities = [ ] } = await chrome . storage . local . get ( [ 'activities' ] ) ;
157- const updated = [ ...newActivities , ...activities ] . slice ( 0 , 100 ) ; // Keep last 100
199+
200+ // Merge new activities, avoiding duplicates
201+ const existingIds = new Set ( activities . map ( a => a . id ) ) ;
202+ const uniqueNew = newActivities . filter ( a => ! existingIds . has ( a . id ) ) ;
203+
204+ const updated = [ ...uniqueNew , ...activities ] . slice ( 0 , 100 ) ;
158205 await chrome . storage . local . set ( { activities : updated } ) ;
159206}
160207
161- function updateBadge ( count ) {
162- chrome . action . setBadgeText ( { text : count > 0 ? count . toString ( ) : '' } ) ;
208+ async function updateBadge ( count ) {
209+ const { readItems = [ ] } = await chrome . storage . local . get ( [ 'readItems' ] ) ;
210+ const { activities = [ ] } = await chrome . storage . local . get ( [ 'activities' ] ) ;
211+
212+ const unreadCount = activities . filter ( a => ! readItems . includes ( a . id ) ) . length ;
213+
214+ chrome . action . setBadgeText ( { text : unreadCount > 0 ? unreadCount . toString ( ) : '' } ) ;
163215 chrome . action . setBadgeBackgroundColor ( { color : '#0366d6' } ) ;
164216}
165217
@@ -203,4 +255,44 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
203255 chrome . action . setBadgeText ( { text : '' } ) ;
204256 sendResponse ( { success : true } ) ;
205257 }
258+
259+ if ( request . action === 'markAsRead' ) {
260+ chrome . storage . local . get ( [ 'readItems' ] , ( result ) => {
261+ const readItems = result . readItems || [ ] ;
262+ if ( ! readItems . includes ( request . id ) ) {
263+ readItems . push ( request . id ) ;
264+ chrome . storage . local . set ( { readItems } , ( ) => {
265+ updateBadge ( ) ;
266+ sendResponse ( { success : true } ) ;
267+ } ) ;
268+ } else {
269+ sendResponse ( { success : true } ) ;
270+ }
271+ } ) ;
272+ return true ;
273+ }
274+
275+ if ( request . action === 'markAsUnread' ) {
276+ chrome . storage . local . get ( [ 'readItems' ] , ( result ) => {
277+ const readItems = result . readItems || [ ] ;
278+ const updated = readItems . filter ( id => id !== request . id ) ;
279+ chrome . storage . local . set ( { readItems : updated } , ( ) => {
280+ updateBadge ( ) ;
281+ sendResponse ( { success : true } ) ;
282+ } ) ;
283+ } ) ;
284+ return true ;
285+ }
286+
287+ if ( request . action === 'markAllAsRead' ) {
288+ chrome . storage . local . get ( [ 'activities' ] , ( result ) => {
289+ const activities = result . activities || [ ] ;
290+ const allIds = activities . map ( a => a . id ) ;
291+ chrome . storage . local . set ( { readItems : allIds } , ( ) => {
292+ updateBadge ( ) ;
293+ sendResponse ( { success : true } ) ;
294+ } ) ;
295+ } ) ;
296+ return true ;
297+ }
206298} ) ;
0 commit comments