@@ -48,6 +48,59 @@ export default class DGitProvider extends Plugin<any, CustomRemixApi> {
48
48
return await this . call ( 'config' as any , 'getAppParameter' , 'settings/gist-access-token' )
49
49
}
50
50
51
+ private isValidGitHubToken ( token : string ) : boolean {
52
+ if ( ! token || typeof token !== 'string' ) {
53
+ return false
54
+ }
55
+
56
+ // Remove whitespace
57
+ token = token . trim ( )
58
+
59
+ // Check for empty token
60
+ if ( token . length === 0 ) {
61
+ return false
62
+ }
63
+
64
+ // GitHub token patterns:
65
+ // Personal Access Token (classic): ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (40 chars)
66
+ // Personal Access Token (fine-grained): github_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (varies)
67
+ // OAuth token: gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (40 chars)
68
+ // GitHub App token: ghs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (40 chars)
69
+ // GitHub App installation token: ghu_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (40 chars)
70
+ // Refresh token: ghr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (40 chars)
71
+
72
+ const tokenPatterns = [
73
+ / ^ g h p _ [ A - Z a - z 0 - 9 ] { 36 } $ / , // Personal Access Token (classic)
74
+ / ^ g i t h u b _ p a t _ [ A - Z a - z 0 - 9 _ ] { 22 , 255 } $ / , // Personal Access Token (fine-grained)
75
+ / ^ g h o _ [ A - Z a - z 0 - 9 ] { 36 } $ / , // OAuth token
76
+ / ^ g h s _ [ A - Z a - z 0 - 9 ] { 36 } $ / , // GitHub App token
77
+ / ^ g h u _ [ A - Z a - z 0 - 9 ] { 36 } $ / , // GitHub App installation token
78
+ / ^ g h r _ [ A - Z a - z 0 - 9 ] { 36 } $ / , // Refresh token
79
+ ]
80
+
81
+ // Check if token matches any known GitHub token pattern
82
+ const matchesPattern = tokenPatterns . some ( pattern => pattern . test ( token ) )
83
+
84
+ if ( matchesPattern ) {
85
+ return true
86
+ }
87
+
88
+ // Fallback: check if it looks like a legacy token (40 chars, alphanumeric)
89
+ // Some older tokens might not follow the new prefixed format
90
+ if ( / ^ [ A - Z a - z 0 - 9 ] { 40 } $ / . test ( token ) ) {
91
+ console . warn ( 'Token appears to be a legacy GitHub token format' )
92
+ return true
93
+ }
94
+
95
+ console . warn ( 'Token does not match known GitHub token patterns:' , {
96
+ length : token . length ,
97
+ prefix : token . substring ( 0 , 4 ) ,
98
+ hasValidChars : / ^ [ A - Z a - z 0 - 9 _ ] + $ / . test ( token )
99
+ } )
100
+
101
+ return false
102
+ }
103
+
51
104
async getAuthor ( input ) {
52
105
const author : author = {
53
106
name : '' ,
@@ -568,19 +621,32 @@ export default class DGitProvider extends Plugin<any, CustomRemixApi> {
568
621
// OCTOKIT FEATURES
569
622
570
623
async remotebranches ( input : { owner : string , repo : string , token : string , page : number , per_page : number } ) {
624
+ try {
625
+ // Quick validation to avoid unnecessary API calls
626
+ if ( ! this . isValidGitHubToken ( input . token ) ) {
627
+ throw new Error ( 'Invalid GitHub token format' )
628
+ }
571
629
572
- const octokit = new Octokit ( {
573
- auth : input . token
574
- } )
630
+ const octokit = new Octokit ( {
631
+ auth : input . token ,
632
+ retry : { enabled : false }
633
+ } )
575
634
576
- const data = await octokit . request ( 'GET /repos/{owner}/{repo}/branches{?protected,per_page,page}' , {
577
- owner : input . owner ,
578
- repo : input . repo ,
579
- per_page : input . per_page || 100 ,
580
- page : input . page || 1
581
- } )
635
+ const data = await octokit . request ( 'GET /repos/{owner}/{repo}/branches{?protected,per_page,page}' , {
636
+ owner : input . owner ,
637
+ repo : input . repo ,
638
+ per_page : input . per_page || 100 ,
639
+ page : input . page || 1
640
+ } )
582
641
583
- return data . data
642
+ return data . data
643
+ } catch ( e ) {
644
+ console . error ( 'Error fetching remote branches:' , e )
645
+ if ( e . status === 403 ) {
646
+ console . error ( 'GitHub API returned 403 Forbidden - check token permissions or rate limits' )
647
+ }
648
+ throw e // Re-throw to let caller handle
649
+ }
584
650
}
585
651
586
652
async getGitHubUser ( input : { token : string } ) : Promise < {
@@ -589,115 +655,159 @@ export default class DGitProvider extends Plugin<any, CustomRemixApi> {
589
655
scopes : string [ ]
590
656
} > {
591
657
try {
658
+ // Quick validation to avoid unnecessary API calls
659
+ if ( ! this . isValidGitHubToken ( input . token ) ) {
660
+ console . warn ( 'Invalid GitHub token format, skipping API call' )
661
+ return null
662
+ }
663
+
592
664
const octokit = new Octokit ( {
593
- auth : input . token
665
+ auth : input . token ,
666
+ retry : { enabled : false }
594
667
} )
595
668
596
669
const user = await octokit . request ( 'GET /user' , {
597
670
headers : {
598
671
'X-GitHub-Api-Version' : '2022-11-28'
599
672
}
600
673
} )
601
- const emails = await octokit . request ( 'GET /user/emails' )
674
+
675
+ let emails = { data : [ ] }
676
+ try {
677
+ emails = await octokit . request ( 'GET /user/emails' )
678
+ } catch ( emailError ) {
679
+ console . warn ( 'Could not fetch user emails:' , emailError )
680
+ // Continue without emails if this fails
681
+ }
602
682
603
683
const scopes = user . headers [ 'x-oauth-scopes' ] || ''
604
684
605
685
return {
606
686
user : {
607
- ...user . data , isConnected :
608
- user . data . login !== undefined && user . data . login !== null && user . data . login !== ''
687
+ ...user . data ,
688
+ isConnected : user . data . login !== undefined && user . data . login !== null && user . data . login !== ''
609
689
} ,
610
690
emails : emails . data ,
611
691
scopes : scopes && scopes . split ( ',' ) . map ( scope => scope . trim ( ) )
612
692
}
613
693
} catch ( e ) {
694
+ console . error ( 'Error in getGitHubUser:' , e )
695
+ // Check if it's a 403 specifically
696
+ if ( e . status === 403 ) {
697
+ console . error ( 'GitHub API returned 403 Forbidden - check token permissions' )
698
+ }
614
699
return null
615
700
}
616
701
}
617
702
618
703
async remotecommits ( input : remoteCommitsInputType ) : Promise < pagedCommits [ ] > {
704
+ try {
705
+ // Quick validation to avoid unnecessary API calls
706
+ if ( ! this . isValidGitHubToken ( input . token ) ) {
707
+ throw new Error ( 'Invalid GitHub token format' )
708
+ }
619
709
620
- const octokit = new Octokit ( {
621
- auth : input . token
622
- } )
623
- input . length = input . length || 5
624
- input . page = input . page || 1
625
- const response = await octokit . request ( 'GET /repos/{owner}/{repo}/commits' , {
626
- owner : input . owner ,
627
- repo : input . repo ,
628
- sha : input . branch ,
629
- per_page : input . length ,
630
- page : input . page
631
- } )
632
- const pages : pagedCommits [ ] = [ ]
633
- const readCommitResults : ReadCommitResult [ ] = [ ]
634
- for ( const githubApiCommit of response . data ) {
635
- const readCommitResult = {
636
- oid : githubApiCommit . sha ,
637
- commit : {
638
- author : {
639
- name : githubApiCommit . commit . author . name ,
640
- email : githubApiCommit . commit . author . email ,
641
- timestamp : new Date ( githubApiCommit . commit . author . date ) . getTime ( ) / 1000 ,
642
- timezoneOffset : new Date ( githubApiCommit . commit . author . date ) . getTimezoneOffset ( )
643
- } ,
644
- committer : {
645
- name : githubApiCommit . commit . committer . name ,
646
- email : githubApiCommit . commit . committer . email ,
647
- timestamp : new Date ( githubApiCommit . commit . committer . date ) . getTime ( ) / 1000 ,
648
- timezoneOffset : new Date ( githubApiCommit . commit . committer . date ) . getTimezoneOffset ( )
710
+ const octokit = new Octokit ( {
711
+ auth : input . token ,
712
+ retry : { enabled : false }
713
+ } )
714
+ input . length = input . length || 5
715
+ input . page = input . page || 1
716
+ const response = await octokit . request ( 'GET /repos/{owner}/{repo}/commits' , {
717
+ owner : input . owner ,
718
+ repo : input . repo ,
719
+ sha : input . branch ,
720
+ per_page : input . length ,
721
+ page : input . page
722
+ } )
723
+ const pages : pagedCommits [ ] = [ ]
724
+ const readCommitResults : ReadCommitResult [ ] = [ ]
725
+ for ( const githubApiCommit of response . data ) {
726
+ const readCommitResult = {
727
+ oid : githubApiCommit . sha ,
728
+ commit : {
729
+ author : {
730
+ name : githubApiCommit . commit . author . name ,
731
+ email : githubApiCommit . commit . author . email ,
732
+ timestamp : new Date ( githubApiCommit . commit . author . date ) . getTime ( ) / 1000 ,
733
+ timezoneOffset : new Date ( githubApiCommit . commit . author . date ) . getTimezoneOffset ( )
734
+ } ,
735
+ committer : {
736
+ name : githubApiCommit . commit . committer . name ,
737
+ email : githubApiCommit . commit . committer . email ,
738
+ timestamp : new Date ( githubApiCommit . commit . committer . date ) . getTime ( ) / 1000 ,
739
+ timezoneOffset : new Date ( githubApiCommit . commit . committer . date ) . getTimezoneOffset ( )
740
+ } ,
741
+ message : githubApiCommit . commit . message ,
742
+ tree : githubApiCommit . commit . tree . sha ,
743
+ parent : githubApiCommit . parents . map ( parent => parent . sha )
649
744
} ,
650
- message : githubApiCommit . commit . message ,
651
- tree : githubApiCommit . commit . tree . sha ,
652
- parent : githubApiCommit . parents . map ( parent => parent . sha )
653
- } ,
654
- payload : '' // You may need to reconstruct the commit object in Git's format if necessary
745
+ payload : '' // You may need to reconstruct the commit object in Git's format if necessary
746
+ }
747
+ readCommitResults . push ( readCommitResult )
655
748
}
656
- readCommitResults . push ( readCommitResult )
657
- }
658
749
659
- // Check for the Link header to determine pagination
660
- const linkHeader = response . headers . link ;
750
+ // Check for the Link header to determine pagination
751
+ const linkHeader = response . headers . link ;
661
752
662
- let hasNextPage = false ;
663
- if ( linkHeader ) {
664
- // A simple check for the presence of a 'next' relation in the Link header
665
- hasNextPage = linkHeader . includes ( 'rel="next"' ) ;
666
- }
753
+ let hasNextPage = false ;
754
+ if ( linkHeader ) {
755
+ // A simple check for the presence of a 'next' relation in the Link header
756
+ hasNextPage = linkHeader . includes ( 'rel="next"' ) ;
757
+ }
667
758
668
- pages . push ( {
669
- page : input . page ,
670
- perPage : input . length ,
671
- total : response . data . length ,
672
- hasNextPage : hasNextPage ,
673
- commits : readCommitResults
674
- } )
675
- return pages
759
+ pages . push ( {
760
+ page : input . page ,
761
+ perPage : input . length ,
762
+ total : response . data . length ,
763
+ hasNextPage : hasNextPage ,
764
+ commits : readCommitResults
765
+ } )
766
+ return pages
767
+ } catch ( e ) {
768
+ console . error ( 'Error fetching remote commits:' , e )
769
+ if ( e . status === 403 ) {
770
+ console . error ( 'GitHub API returned 403 Forbidden - check token permissions or rate limits' )
771
+ }
772
+ throw e // Re-throw to let caller handle
773
+ }
676
774
}
677
775
678
776
async repositories ( input : repositoriesInput ) {
777
+ try {
778
+ // Quick validation to avoid unnecessary API calls
779
+ if ( ! this . isValidGitHubToken ( input . token ) ) {
780
+ throw new Error ( 'Invalid GitHub token format' )
781
+ }
679
782
680
- const accessToken = input . token ;
783
+ const accessToken = input . token ;
681
784
682
- const page = input . page || 1
683
- const perPage = input . per_page || 10
785
+ const page = input . page || 1
786
+ const perPage = input . per_page || 10
684
787
685
- const baseURL = 'https://api.github.com/user/repos'
686
- const repositories = [ ]
687
- const sort = 'updated'
688
- const direction = 'desc'
788
+ const baseURL = 'https://api.github.com/user/repos'
789
+ const repositories = [ ]
790
+ const sort = 'updated'
791
+ const direction = 'desc'
689
792
690
- const headers = {
691
- 'Authorization' : `Bearer ${ accessToken } ` , // Include your GitHub access token
692
- 'Accept' : 'application/vnd.github.v3+json' , // GitHub API v3 media type
693
- } ;
793
+ const headers = {
794
+ 'Authorization' : `Bearer ${ accessToken } ` , // Include your GitHub access token
795
+ 'Accept' : 'application/vnd.github.v3+json' , // GitHub API v3 media type
796
+ } ;
694
797
695
- const url = `${ baseURL } ?visibility=private,public&page=${ page } &per_page=${ perPage } &sort=${ sort } &direction=${ direction } ` ;
696
- const response = await axios . get ( url , { headers } ) ;
798
+ const url = `${ baseURL } ?visibility=private,public&page=${ page } &per_page=${ perPage } &sort=${ sort } &direction=${ direction } ` ;
799
+ const response = await axios . get ( url , { headers } ) ;
697
800
698
- repositories . push ( ...response . data ) ;
801
+ repositories . push ( ...response . data ) ;
699
802
700
- return repositories
803
+ return repositories
804
+ } catch ( e ) {
805
+ console . error ( 'Error fetching repositories:' , e )
806
+ if ( e . response ?. status === 403 ) {
807
+ console . error ( 'GitHub API returned 403 Forbidden - check token permissions or rate limits' )
808
+ }
809
+ throw e // Re-throw to let caller handle
810
+ }
701
811
}
702
812
703
813
}
0 commit comments