Skip to content

Commit 47e56ff

Browse files
authored
Merge branch 'master' into extend-modal
2 parents 95b5f9d + 7237872 commit 47e56ff

File tree

8 files changed

+220
-92
lines changed

8 files changed

+220
-92
lines changed

apps/remix-ide/src/app/files/dgitProvider.ts

Lines changed: 190 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,59 @@ export default class DGitProvider extends Plugin<any, CustomRemixApi> {
4848
return await this.call('config' as any, 'getAppParameter', 'settings/gist-access-token')
4949
}
5050

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+
/^ghp_[A-Za-z0-9]{36}$/, // Personal Access Token (classic)
74+
/^github_pat_[A-Za-z0-9_]{22,255}$/, // Personal Access Token (fine-grained)
75+
/^gho_[A-Za-z0-9]{36}$/, // OAuth token
76+
/^ghs_[A-Za-z0-9]{36}$/, // GitHub App token
77+
/^ghu_[A-Za-z0-9]{36}$/, // GitHub App installation token
78+
/^ghr_[A-Za-z0-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-Za-z0-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-Za-z0-9_]+$/.test(token)
99+
})
100+
101+
return false
102+
}
103+
51104
async getAuthor(input) {
52105
const author: author = {
53106
name: '',
@@ -568,19 +621,32 @@ export default class DGitProvider extends Plugin<any, CustomRemixApi> {
568621
// OCTOKIT FEATURES
569622

570623
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+
}
571629

572-
const octokit = new Octokit({
573-
auth: input.token
574-
})
630+
const octokit = new Octokit({
631+
auth: input.token,
632+
retry: { enabled: false }
633+
})
575634

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+
})
582641

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+
}
584650
}
585651

586652
async getGitHubUser(input: { token: string }): Promise<{
@@ -589,115 +655,159 @@ export default class DGitProvider extends Plugin<any, CustomRemixApi> {
589655
scopes: string[]
590656
}> {
591657
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+
592664
const octokit = new Octokit({
593-
auth: input.token
665+
auth: input.token,
666+
retry: { enabled: false }
594667
})
595668

596669
const user = await octokit.request('GET /user', {
597670
headers: {
598671
'X-GitHub-Api-Version': '2022-11-28'
599672
}
600673
})
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+
}
602682

603683
const scopes = user.headers['x-oauth-scopes'] || ''
604684

605685
return {
606686
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 !== ''
609689
},
610690
emails: emails.data,
611691
scopes: scopes && scopes.split(',').map(scope => scope.trim())
612692
}
613693
} 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+
}
614699
return null
615700
}
616701
}
617702

618703
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+
}
619709

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)
649744
},
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)
655748
}
656-
readCommitResults.push(readCommitResult)
657-
}
658749

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;
661752

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+
}
667758

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+
}
676774
}
677775

678776
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+
}
679782

680-
const accessToken = input.token;
783+
const accessToken = input.token;
681784

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
684787

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'
689792

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+
};
694797

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 });
697800

698-
repositories.push(...response.data);
801+
repositories.push(...response.data);
699802

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+
}
701811
}
702812

703813
}

apps/remix-ide/src/app/tabs/locales/en/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@
5757
"settings.analyticsDescription": "Control how Remix uses AI and analytics to improve your experience.",
5858
"settings.aiDescription": "The Remix AI Assistant enhances your coding experience with smart suggestions and automated insights. Manage how AI interacts with your code and data.",
5959
"settings.servicesDescription": "Configure the settings for connected services, including Github, IPFS, Swarm, Sindri and Etherscan.",
60-
"settings.matomoAnalyticsNoCookies": "Matomo Analytics (no cookies)",
60+
"settings.matomoAnalyticsNoCookies": "Matomo Analytics (necessary, no cookies)",
6161
"settings.matomoAnalyticsNoCookiesDescription": "Help improve Remix with anonymous usage data.",
62-
"settings.matomoAnalyticsWithCookies": "Matomo Analytics (with cookies)",
62+
"settings.matomoAnalyticsWithCookies": "Matomo Performance Analytics (with cookies)",
6363
"settings.matomoAnalyticsWithCookiesDescription": "Enable tracking with cookies for more detailed insights.",
6464
"settings.aiCopilot": "AI Copilot",
6565
"settings.aiCopilotDescription": "AI Copilot assists with code suggestions and improvements.",

apps/remix-ide/src/app/tabs/settings-tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const _paq = (window._paq = window._paq || [])
1515
const profile = {
1616
name: 'settings',
1717
displayName: 'Settings',
18-
methods: ['get', 'updateCopilotChoice', 'getCopilotSetting'],
18+
methods: ['get', 'updateCopilotChoice', 'getCopilotSetting', 'updateMatomoPerfAnalyticsChoice'],
1919
events: [],
2020
icon: 'assets/img/settings.webp',
2121
description: 'Remix-IDE settings',

apps/remix-ide/src/app/tabs/web3-provider.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class Web3ProviderModule extends Plugin {
5555
if (payload.method === 'eth_sendTransaction') {
5656
if (payload.params.length && !payload.params[0].to && message.result) {
5757
setTimeout(async () => {
58+
this.emit('transactionBroadcasted', message.result)
5859
const receipt = await this.tryTillReceiptAvailable(message.result)
5960
if (!receipt.contractAddress) {
6061
console.log('receipt available but contract address not present', receipt)

apps/remix-ide/src/blockchain/blockchain.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,8 +797,29 @@ export class Blockchain extends Plugin {
797797
(_) => this.executionContext.currentblockGasLimit()
798798
)
799799

800+
const logTransaction = (txhash, origin) => {
801+
this.detectNetwork((error, network) => {
802+
console.log(`transaction sent: ${txhash}`, network)
803+
if (network && network.id) {
804+
_paq.push(['trackEvent', 'udapp', `sendTransaction-from-${origin}`, `${txhash}-${network.id}`])
805+
} else {
806+
try {
807+
const networkString = JSON.stringify(network)
808+
_paq.push(['trackEvent', 'udapp', `sendTransaction-from-${origin}`, `${txhash}-${networkString}`])
809+
} catch (e) {
810+
_paq.push(['trackEvent', 'udapp', `sendTransaction-from-${origin}`, `${txhash}-unknownnetwork`])
811+
}
812+
}
813+
})
814+
}
815+
816+
this.on('web3Provider', 'transactionBroadcasted', (txhash) => {
817+
logTransaction(txhash, 'plugin')
818+
})
819+
800820
web3Runner.event.register('transactionBroadcasted', (txhash, isUserOp) => {
801821
if (isUserOp) _paq.push(['trackEvent', 'udapp', 'safeSmartAccount', `txBroadcastedFromSmartAccount`])
822+
logTransaction(txhash, 'gui')
802823
this.executionContext.detectNetwork(async (error, network) => {
803824
if (error || !network) return
804825
if (network.name === 'VM') return

0 commit comments

Comments
 (0)