@@ -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,163 @@ 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
+
664
+
592
665
const octokit = new Octokit ( {
593
- auth : input . token
666
+ auth : input . token ,
667
+ retry : { enabled : false }
594
668
} )
595
669
596
670
const user = await octokit . request ( 'GET /user' , {
597
671
headers : {
598
672
'X-GitHub-Api-Version' : '2022-11-28'
599
673
}
600
674
} )
601
- const emails = await octokit . request ( 'GET /user/emails' )
675
+
676
+
677
+ let emails = { data : [ ] }
678
+ try {
679
+ emails = await octokit . request ( 'GET /user/emails' )
680
+ } catch ( emailError ) {
681
+ console . warn ( 'Could not fetch user emails:' , emailError )
682
+ // Continue without emails if this fails
683
+ }
602
684
603
685
const scopes = user . headers [ 'x-oauth-scopes' ] || ''
604
686
605
687
return {
606
688
user : {
607
- ...user . data , isConnected :
608
- user . data . login !== undefined && user . data . login !== null && user . data . login !== ''
689
+ ...user . data ,
690
+ isConnected : user . data . login !== undefined && user . data . login !== null && user . data . login !== ''
609
691
} ,
610
692
emails : emails . data ,
611
693
scopes : scopes && scopes . split ( ',' ) . map ( scope => scope . trim ( ) )
612
694
}
613
695
} catch ( e ) {
696
+ console . error ( 'Error in getGitHubUser:' , e )
697
+ // Check if it's a 403 specifically
698
+ if ( e . status === 403 ) {
699
+ console . error ( 'GitHub API returned 403 Forbidden - check token permissions' )
700
+ }
614
701
return null
615
702
}
616
703
}
617
704
705
+
706
+
618
707
async remotecommits ( input : remoteCommitsInputType ) : Promise < pagedCommits [ ] > {
708
+ try {
709
+ // Quick validation to avoid unnecessary API calls
710
+ if ( ! this . isValidGitHubToken ( input . token ) ) {
711
+ throw new Error ( 'Invalid GitHub token format' )
712
+ }
619
713
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 ( )
714
+ const octokit = new Octokit ( {
715
+ auth : input . token ,
716
+ retry : { enabled : false }
717
+ } )
718
+ input . length = input . length || 5
719
+ input . page = input . page || 1
720
+ const response = await octokit . request ( 'GET /repos/{owner}/{repo}/commits' , {
721
+ owner : input . owner ,
722
+ repo : input . repo ,
723
+ sha : input . branch ,
724
+ per_page : input . length ,
725
+ page : input . page
726
+ } )
727
+ const pages : pagedCommits [ ] = [ ]
728
+ const readCommitResults : ReadCommitResult [ ] = [ ]
729
+ for ( const githubApiCommit of response . data ) {
730
+ const readCommitResult = {
731
+ oid : githubApiCommit . sha ,
732
+ commit : {
733
+ author : {
734
+ name : githubApiCommit . commit . author . name ,
735
+ email : githubApiCommit . commit . author . email ,
736
+ timestamp : new Date ( githubApiCommit . commit . author . date ) . getTime ( ) / 1000 ,
737
+ timezoneOffset : new Date ( githubApiCommit . commit . author . date ) . getTimezoneOffset ( )
738
+ } ,
739
+ committer : {
740
+ name : githubApiCommit . commit . committer . name ,
741
+ email : githubApiCommit . commit . committer . email ,
742
+ timestamp : new Date ( githubApiCommit . commit . committer . date ) . getTime ( ) / 1000 ,
743
+ timezoneOffset : new Date ( githubApiCommit . commit . committer . date ) . getTimezoneOffset ( )
744
+ } ,
745
+ message : githubApiCommit . commit . message ,
746
+ tree : githubApiCommit . commit . tree . sha ,
747
+ parent : githubApiCommit . parents . map ( parent => parent . sha )
649
748
} ,
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
749
+ payload : '' // You may need to reconstruct the commit object in Git's format if necessary
750
+ }
751
+ readCommitResults . push ( readCommitResult )
655
752
}
656
- readCommitResults . push ( readCommitResult )
657
- }
658
753
659
- // Check for the Link header to determine pagination
660
- const linkHeader = response . headers . link ;
754
+ // Check for the Link header to determine pagination
755
+ const linkHeader = response . headers . link ;
661
756
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
- }
757
+ let hasNextPage = false ;
758
+ if ( linkHeader ) {
759
+ // A simple check for the presence of a 'next' relation in the Link header
760
+ hasNextPage = linkHeader . includes ( 'rel="next"' ) ;
761
+ }
667
762
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
763
+ pages . push ( {
764
+ page : input . page ,
765
+ perPage : input . length ,
766
+ total : response . data . length ,
767
+ hasNextPage : hasNextPage ,
768
+ commits : readCommitResults
769
+ } )
770
+ return pages
771
+ } catch ( e ) {
772
+ console . error ( 'Error fetching remote commits:' , e )
773
+ if ( e . status === 403 ) {
774
+ console . error ( 'GitHub API returned 403 Forbidden - check token permissions or rate limits' )
775
+ }
776
+ throw e // Re-throw to let caller handle
777
+ }
676
778
}
677
779
678
780
async repositories ( input : repositoriesInput ) {
781
+ try {
782
+ // Quick validation to avoid unnecessary API calls
783
+ if ( ! this . isValidGitHubToken ( input . token ) ) {
784
+ throw new Error ( 'Invalid GitHub token format' )
785
+ }
679
786
680
- const accessToken = input . token ;
787
+ const accessToken = input . token ;
681
788
682
- const page = input . page || 1
683
- const perPage = input . per_page || 10
789
+ const page = input . page || 1
790
+ const perPage = input . per_page || 10
684
791
685
- const baseURL = 'https://api.github.com/user/repos'
686
- const repositories = [ ]
687
- const sort = 'updated'
688
- const direction = 'desc'
792
+ const baseURL = 'https://api.github.com/user/repos'
793
+ const repositories = [ ]
794
+ const sort = 'updated'
795
+ const direction = 'desc'
689
796
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
- } ;
797
+ const headers = {
798
+ 'Authorization' : `Bearer ${ accessToken } ` , // Include your GitHub access token
799
+ 'Accept' : 'application/vnd.github.v3+json' , // GitHub API v3 media type
800
+ } ;
694
801
695
- const url = `${ baseURL } ?visibility=private,public&page=${ page } &per_page=${ perPage } &sort=${ sort } &direction=${ direction } ` ;
696
- const response = await axios . get ( url , { headers } ) ;
802
+ const url = `${ baseURL } ?visibility=private,public&page=${ page } &per_page=${ perPage } &sort=${ sort } &direction=${ direction } ` ;
803
+ const response = await axios . get ( url , { headers } ) ;
697
804
698
- repositories . push ( ...response . data ) ;
805
+ repositories . push ( ...response . data ) ;
699
806
700
- return repositories
807
+ return repositories
808
+ } catch ( e ) {
809
+ console . error ( 'Error fetching repositories:' , e )
810
+ if ( e . response ?. status === 403 ) {
811
+ console . error ( 'GitHub API returned 403 Forbidden - check token permissions or rate limits' )
812
+ }
813
+ throw e // Re-throw to let caller handle
814
+ }
701
815
}
702
816
703
817
}
0 commit comments