From 09127321f0e7c8f98f6a75d8cf760f98a8a97456 Mon Sep 17 00:00:00 2001 From: Spencer Murray <159931558+spalmurray-codecov@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:36:03 -0500 Subject: [PATCH] feat: Overhaul popup menu and provider management (#103) This PR is primarily meant to fix a bug where github enterprise URLs would not actually work due to 'github' being the hardcoded provider on API requests. While I was in here though I took it as a chance to rewrite the popup menu to address some other issues. --- package-lock.json | 151 -------- package.json | 2 - src/background/main.ts | 11 +- src/constants.ts | 11 +- src/content/github/common/fetchers.ts | 5 - src/popup/main.tsx | 507 +++++++++++++------------- src/popup/styles.css | 29 ++ src/service.ts | 170 +++++---- tailwind.config.js | 18 +- 9 files changed, 398 insertions(+), 506 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c69cbc..c82e7a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "code-tag": "^1.1.0", "color-alpha": "^1.1.3", "dom-chef": "^5.1.0", - "fetch-intercept": "^2.4.0", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -37,7 +36,6 @@ "autoprefixer": "^10.4.14", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.7.3", - "daisyui": "^2.51.3", "postcss": "^8.4.21", "postcss-loader": "^7.0.2", "prettier": "^2.2.1", @@ -1617,19 +1615,6 @@ "url": "https://github.com/sponsors/fregante" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-alpha": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-alpha/-/color-alpha-1.1.3.tgz", @@ -1663,16 +1648,6 @@ "color-name": "^1.0.0" } }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dev": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/colorette": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", @@ -1771,16 +1746,6 @@ "webpack": "^5.0.0" } }, - "node_modules/css-selector-tokenizer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", - "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1798,26 +1763,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "node_modules/daisyui": { - "version": "2.51.3", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.51.3.tgz", - "integrity": "sha512-AQa9exq/DsnvjyDi6bwOqHExQr9LJJag0iKRXNvRRtHXPo1gaAQ3ASJWylUB8J8KMH2M9zIpr7cvPHc7yGckyQ==", - "dev": true, - "dependencies": { - "color": "^4.2", - "css-selector-tokenizer": "^0.8.0", - "postcss-js": "^4.0.0", - "tailwindcss": "^3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/daisyui" - }, - "peerDependencies": { - "autoprefixer": "^10.0.2", - "postcss": "^8.1.6" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2123,12 +2068,6 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, - "node_modules/fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -2138,11 +2077,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-intercept": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/fetch-intercept/-/fetch-intercept-2.4.0.tgz", - "integrity": "sha512-BPZ2LM9Dh1ua2ovQf03N6rhWg1qxdVD5qK/G4llvcemt6M+jjxCuIDxJ+6IiG+uz//3UQmgfKEv0gOGvYIxZ7g==" - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3607,21 +3541,6 @@ "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", "dev": true }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -5474,16 +5393,6 @@ "resolved": "https://registry.npmjs.org/code-tag/-/code-tag-1.1.0.tgz", "integrity": "sha512-qqvyRC9Fmnqy/1nK2Jz6FIk6F24nliVIVtQFg0r7PuZCZHfWO/c7eZHVlPxFKRSnOSIUUf/jrF1FG8j67FinPg==" }, - "color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "requires": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - } - }, "color-alpha": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-alpha/-/color-alpha-1.1.3.tgz", @@ -5514,16 +5423,6 @@ "color-name": "^1.0.0" } }, - "color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dev": true, - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "colorette": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", @@ -5603,16 +5502,6 @@ "semver": "^7.3.8" } }, - "css-selector-tokenizer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", - "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2" - } - }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5624,18 +5513,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "daisyui": { - "version": "2.51.3", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.51.3.tgz", - "integrity": "sha512-AQa9exq/DsnvjyDi6bwOqHExQr9LJJag0iKRXNvRRtHXPo1gaAQ3ASJWylUB8J8KMH2M9zIpr7cvPHc7yGckyQ==", - "dev": true, - "requires": { - "color": "^4.2", - "css-selector-tokenizer": "^0.8.0", - "postcss-js": "^4.0.0", - "tailwindcss": "^3" - } - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5873,12 +5750,6 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -5888,11 +5759,6 @@ "reusify": "^1.0.4" } }, - "fetch-intercept": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/fetch-intercept/-/fetch-intercept-2.4.0.tgz", - "integrity": "sha512-BPZ2LM9Dh1ua2ovQf03N6rhWg1qxdVD5qK/G4llvcemt6M+jjxCuIDxJ+6IiG+uz//3UQmgfKEv0gOGvYIxZ7g==" - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6941,23 +6807,6 @@ "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", "dev": true }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dev": true, - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - } - } - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index 0bdeb61..3e4292d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "code-tag": "^1.1.0", "color-alpha": "^1.1.3", "dom-chef": "^5.1.0", - "fetch-intercept": "^2.4.0", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -39,7 +38,6 @@ "autoprefixer": "^10.4.14", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.7.3", - "daisyui": "^2.51.3", "postcss": "^8.4.21", "postcss-loader": "^7.0.2", "prettier": "^2.2.1", diff --git a/src/background/main.ts b/src/background/main.ts index 5b89fca..519141d 100644 --- a/src/background/main.ts +++ b/src/background/main.ts @@ -32,18 +32,19 @@ async function handleMessages(message: { payload: any; referrer?: string; }) { + const codecov = new Codecov(); return Sentry.startSpan({ name: message.type }, async () => { switch (message.type) { case MessageType.FETCH_COMMIT_REPORT: - return Codecov.fetchCommitReport(message.payload, message.referrer!); + return codecov.fetchCommitReport(message.payload, message.referrer!); case MessageType.FETCH_PR_COMPARISON: - return Codecov.fetchPRComparison(message.payload, message.referrer!); + return codecov.fetchPRComparison(message.payload, message.referrer!); case MessageType.FETCH_FLAGS_LIST: - return Codecov.listFlags(message.payload, message.referrer!); + return codecov.listFlags(message.payload, message.referrer!); case MessageType.FETCH_COMPONENTS_LIST: - return Codecov.listComponents(message.payload, message.referrer!); + return codecov.listComponents(message.payload, message.referrer!); case MessageType.CHECK_AUTH: - return Codecov.checkAuth(message.payload); + return codecov.checkAuth(message.payload); case MessageType.REGISTER_CONTENT_SCRIPTS: return registerContentScript(message.payload); case MessageType.UNREGISTER_CONTENT_SCRIPTS: diff --git a/src/constants.ts b/src/constants.ts index 743949e..6230fb0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,12 @@ -export const useSelfHostedStorageKey = "use_self_hosted"; +export const codecovApiTokenStorageKey = "self_hosted_codecov_api_token"; // Keeping this key to not break existing installs. export const selfHostedCodecovURLStorageKey = "self_hosted_codecov_url"; export const selfHostedGitHubURLStorageKey = "self_hosted_github_url"; -export const selfHostedCodecovApiToken = "self_hosted_codecov_api_token"; export const dynamicContentScriptRegistrationId = "dynamic-content-script"; + +export const codecovCloudApiUrl = "https://api.codecov.io"; +export const githubCloudUrl = "https://github.com"; + +export const providers = { + github: "github", + githubEnterprise: "github_enterprise", +} as const; diff --git a/src/content/github/common/fetchers.ts b/src/content/github/common/fetchers.ts index 60b8e97..3b0f1f1 100644 --- a/src/content/github/common/fetchers.ts +++ b/src/content/github/common/fetchers.ts @@ -7,7 +7,6 @@ import { export async function getFlags(metadata: FileMetadata): Promise { const payload = { - service: "github", owner: metadata.owner, repo: metadata.repo, }; @@ -25,7 +24,6 @@ export async function getFlags(metadata: FileMetadata): Promise { export async function getComponents(metadata: FileMetadata): Promise { const payload = { - service: "github", owner: metadata.owner, repo: metadata.repo, }; @@ -52,7 +50,6 @@ export async function getCommitReport( } const payload = { - service: "github", owner: metadata.owner, repo: metadata.repo, path: metadata.path, @@ -74,7 +71,6 @@ export async function getBranchReport( metadata: FileMetadata ): Promise { const payload = { - service: "github", owner: metadata.owner, repo: metadata.repo, path: metadata.path, @@ -92,7 +88,6 @@ export async function getBranchReport( export async function getPRReport(url: any) { const payload = { - service: "github", owner: url.owner, repo: url.repo, pullid: url.id, diff --git a/src/popup/main.tsx b/src/popup/main.tsx index 8cd3f5a..b86f654 100644 --- a/src/popup/main.tsx +++ b/src/popup/main.tsx @@ -6,308 +6,315 @@ import urlJoin from "url-join"; import "./styles.css"; import { - selfHostedCodecovApiToken, + codecovApiTokenStorageKey, selfHostedCodecovURLStorageKey, selfHostedGitHubURLStorageKey, - useSelfHostedStorageKey, + codecovCloudApiUrl, + providers, } from "src/constants"; import { MessageType } from "src/types"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faSquareCheck } from "@fortawesome/free-solid-svg-icons"; -const Popup = () => { - // persisted state +interface ToggleUrlInputProps { + showInputLabel: string; + showInput: boolean; + setShowInput: (value: boolean) => void; + urlInputLabel: string; + urlInputPlaceholder: string; + url: string; + setUrl: (value: string) => void; + errorMessage?: string; + showError?: boolean; +} + +function ToggleUrlInput({ + showInputLabel, + showInput, + setShowInput, + urlInputLabel, + urlInputPlaceholder, + url, + setUrl, + errorMessage, +}: ToggleUrlInputProps) { + const handleToggle = () => { + setShowInput(!showInput); + setUrl(""); + }; + + const handleSpace: React.KeyboardEventHandler = (e) => { + if (e.key === " ") { + setShowInput(!showInput); + setUrl(""); + } + }; + + return ( + <> +
+
+ + +
+ {showInput ? ( +
+ + setUrl(e.target.value)} + className={clsx({ + "border-red-500": errorMessage, + })} + /> + {errorMessage ? ( +

{errorMessage}

+ ) : null} +
+ ) : null} +
+ + ); +} + +function Popup() { + const [codecovApiToken, setCodecovApiToken] = useState(""); const [useSelfHosted, setUseSelfHosted] = useState(false); const [codecovUrl, setCodecovUrl] = useState(""); + const [useGithubEnterprise, setUseGithubEnterprise] = useState(false); const [githubUrl, setGitHubUrl] = useState(""); - const [codecovApiToken, setCodecovApiToken] = useState(""); - // ephemeral state - const [isUrlPermissionGranted, setIsUrlPermissionGranted] = useState(false); - const [isUrlError, setIsUrlError] = useState(false); - const [isTokenError, setIsTokenError] = useState(false); - const [isTabError, setIsTabError] = useState(false); - const [isUnregisterTabError, setIsUnregisterTabError] = useState(false); - const [isDone, setIsDone] = useState(false); - - const isFormInvalid = - useSelfHosted && - !(codecovUrl && codecovApiToken && githubUrl && isUrlPermissionGranted); - const isError = - isUrlError || isTokenError || isTabError || isUnregisterTabError; + const [apiTokenError, setApiTokenError] = useState(""); + const [codecovUrlError, setCodecovUrlError] = useState(""); + const [githubUrlError, setGithubUrlError] = useState(""); + const [hasChanges, setHasChanges] = useState(false); useEffect(() => { loadPersistedState(); }, []); + const withDetectChanges = (setter: (value: string) => void) => { + return (value: string) => { + setHasChanges(true); + setter(value); + }; + }; + const loadPersistedState = () => { browser.storage.sync .get([ - useSelfHostedStorageKey, selfHostedCodecovURLStorageKey, selfHostedGitHubURLStorageKey, - selfHostedCodecovApiToken, + codecovApiTokenStorageKey, ]) .then((result) => { - const _useSelfHosted = result[useSelfHostedStorageKey] || false; - setUseSelfHosted(_useSelfHosted); - const _codecovUrl = result[selfHostedCodecovURLStorageKey] || ""; - setCodecovUrl(_codecovUrl); - const _githubUrl = result[selfHostedGitHubURLStorageKey] || ""; - setGitHubUrl(_githubUrl); - const _codecovApiToken = result[selfHostedCodecovApiToken] || ""; - setCodecovApiToken(_codecovApiToken); + const _codecovApiToken = result[codecovApiTokenStorageKey]; + if (_codecovApiToken) { + setCodecovApiToken(_codecovApiToken); + } + const _codecovUrl = result[selfHostedCodecovURLStorageKey]; + if (_codecovUrl) { + setUseSelfHosted(true); + setCodecovUrl(_codecovUrl); + } + const _githubUrl = result[selfHostedGitHubURLStorageKey]; + if (_githubUrl) { + setUseGithubEnterprise(true); + setGitHubUrl(_githubUrl); + } }); }; - const resetEphemeralState = () => { - setIsUrlPermissionGranted(false); - setIsUrlError(false); - setIsTokenError(false); - setIsTabError(false); - setIsUnregisterTabError(false); - setIsDone(false); - }; - - const handleTextChange = - (setter: React.Dispatch>) => - (e: React.ChangeEvent) => { - resetEphemeralState(); - setter(e.target.value); - }; - - const registerContentScripts = async (url: string) => { - const payload = { - url, - }; - - return browser.runtime.sendMessage({ - type: MessageType.REGISTER_CONTENT_SCRIPTS, - payload, - }); - }; - - const unregisterContentScripts = async () => { - const payload = {}; - - return browser.runtime.sendMessage({ - type: MessageType.UNREGISTER_CONTENT_SCRIPTS, - payload, - }); - }; - - function requestHostPermission() { - const urlMatch = urlJoin(codecovUrl, "/*"); - browser.permissions - .request({ - origins: [urlMatch], - }) - .then(setIsUrlPermissionGranted); - } - const handleSave = async () => { if (useSelfHosted) { - try { - const payload = { - baseUrl: codecovUrl, - token: codecovApiToken, - }; + const urlMatch = urlJoin(codecovUrl, "/*"); + await browser.permissions.request({ + origins: [urlMatch], + }); + } - const isAuthOk = await browser.runtime.sendMessage({ - type: MessageType.CHECK_AUTH, - payload, - }); + if (codecovApiToken && !isValidTokenFormat(codecovApiToken)) { + setApiTokenError( + "Invalid token format. Token should be a 32 hex digit UUID. See this input's placeholder value for en example." + ); + return; + } + + try { + const payload = { + baseUrl: useSelfHosted ? codecovUrl : codecovCloudApiUrl, + token: codecovApiToken, + provider: useGithubEnterprise + ? providers.githubEnterprise + : providers.github, + }; - if (!isAuthOk) { - setIsTokenError(true); - return; + const isAuthOk = await browser.runtime.sendMessage({ + type: MessageType.CHECK_AUTH, + payload, + }); + + if (!isAuthOk) { + setApiTokenError( + "API token authentication failed. Make sure your token is correct." + ); + if (useSelfHosted) { + setCodecovUrlError( + "API token authentication failed. Make sure your self-hosted URL is correct." + ); } - } catch (error) { - setIsUrlError(true); return; } + setApiTokenError(""); + setCodecovUrlError(""); + } catch (error) { + setCodecovUrlError( + "Invalid URL. Make sure your self-hosted URL is correct." + ); + return; + } + if (useGithubEnterprise) { const isScriptRegistered = await registerContentScripts(githubUrl); if (!isScriptRegistered) { - setIsTabError(true); - return; - } - } - - if (!useSelfHosted) { - const isScriptUnregistered = await unregisterContentScripts(); - if (!isScriptUnregistered) { - setIsUnregisterTabError(true); + setGithubUrlError( + "This URL must be loaded in the active tab when you press Save." + ); return; } + } else { + await unregisterContentScripts(); } + setGithubUrlError(""); await browser.storage.sync.set({ - [useSelfHostedStorageKey]: useSelfHosted, + [codecovApiTokenStorageKey]: codecovApiToken, [selfHostedCodecovURLStorageKey]: codecovUrl, [selfHostedGitHubURLStorageKey]: githubUrl, - [selfHostedCodecovApiToken]: codecovApiToken, }); - resetEphemeralState(); - setIsDone(true); - }; - - const handleSelfHostedClick = () => { - resetEphemeralState(); - setUseSelfHosted((x) => !x); - setCodecovUrl(""); - setGitHubUrl(""); - setCodecovApiToken(""); + setHasChanges(false); }; return ( -
-
+
+
+ + +
+
+
+ + + withDetectChanges(setCodecovApiToken)(e.target.value) + } + className={clsx({ + "border-red-500": apiTokenError, + })} + /> + {apiTokenError ? ( +

{apiTokenError}

+ ) : null} +
+ + +
+
+ Issues and feedback are welcome on{" "} - Codecov + GitHub -
- {useSelfHosted && !isDone && ( -
- -
- )} - -
- {/*
*/} - {/* */} - {/*
*/} -
-
-
- - {/*
- -
*/} - {useSelfHosted && ( -
-
- -
- -
- {isUrlError && ( - - )} -
-
- - - {isTokenError && ( - - )} -
-
- - - -
-
- )} -
-
- + !
); +} + +const CodecovLogo = () => ( + + + + +); + +const registerContentScripts = async (url: string) => { + const payload = { + url, + }; + + return browser.runtime.sendMessage({ + type: MessageType.REGISTER_CONTENT_SCRIPTS, + payload, + }); +}; + +const unregisterContentScripts = async () => { + const payload = {}; + + return browser.runtime.sendMessage({ + type: MessageType.UNREGISTER_CONTENT_SCRIPTS, + payload, + }); +}; + +const isValidTokenFormat = (token: string) => { + return /^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/.test(token); }; -createRoot(document.getElementById("app")!).render(); +createRoot(document.getElementById("app")!).render( + <> + + +); diff --git a/src/popup/styles.css b/src/popup/styles.css index b5c61c9..26f774e 100644 --- a/src/popup/styles.css +++ b/src/popup/styles.css @@ -1,3 +1,32 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;500;600;700;800;900&family=Source+Code+Pro:wght@200;300;400;500;600;700;800;900&display=swap"); + @tailwind base; @tailwind components; @tailwind utilities; + +@layer base { + body { + font-family: Poppins, sans-serif; + } + + a { + @apply text-codecov-blue; + } + + label { + @apply text-sm py-2 px-1; + } + + input { + font-family: Source Code Pro, monospace; + @apply text-sm; + } + + input[type="text"] { + @apply border rounded-md p-3; + } + + input[type="checkbox"] { + @apply w-4 h-4; + } +} diff --git a/src/service.ts b/src/service.ts index b98773f..91aac77 100644 --- a/src/service.ts +++ b/src/service.ts @@ -1,85 +1,94 @@ import _ from "lodash"; -import fetchIntercept from "fetch-intercept"; import browser from "webextension-polyfill"; import urlJoin from "url-join"; import { - selfHostedCodecovApiToken, + codecovCloudApiUrl, + codecovApiTokenStorageKey, selfHostedCodecovURLStorageKey, selfHostedGitHubURLStorageKey, - useSelfHostedStorageKey, + providers, } from "src/constants"; export class Codecov { - static baseUrl = "https://api.codecov.io"; - static checkAuthPath = "/api/v2/github/"; - - static _init() { - fetchIntercept.register({ - request: async (requestUrl: string, requestConfig: any) => { - // use request params for auth check - if (new URL(requestUrl).pathname === this.checkAuthPath) { - return [requestUrl, requestConfig]; - } - const result = await browser.storage.sync.get([ - useSelfHostedStorageKey, - selfHostedCodecovURLStorageKey, - selfHostedGitHubURLStorageKey, - selfHostedCodecovApiToken, - ]); - - const useSelfHosted = result[useSelfHostedStorageKey] || false; - // self hosted not selected - if (!useSelfHosted) { - return [requestUrl, requestConfig]; - } - const currentURL = new URL(requestConfig?.headers?.Referrer); - const selfHostedGitHubURL = new URL( - result[selfHostedGitHubURLStorageKey] - ); - // not on self hosted github - if (currentURL.hostname !== selfHostedGitHubURL.hostname) { - return [requestUrl, requestConfig]; - } - const codecovUrl = result[selfHostedCodecovURLStorageKey]; - const codecovApiToken = result[selfHostedCodecovApiToken]; - // update url - const updatedRequestUrl = urlJoin( - codecovUrl, - requestUrl.replace(this.baseUrl, "") - ); - // update auth header - const updatedRequestConfig = _.merge(requestConfig, { - headers: { - Authorization: `bearer ${codecovApiToken}`, - }, - }); - return [updatedRequestUrl, updatedRequestConfig]; - }, - }); + apiToken: string = ""; + apiUrl: string = codecovCloudApiUrl; + provider: typeof providers[keyof typeof providers] = providers.github; + + private async init() { + const result = await browser.storage.sync.get([ + codecovApiTokenStorageKey, + selfHostedCodecovURLStorageKey, + selfHostedGitHubURLStorageKey, + ]); + + const apiToken: string | undefined = result[codecovApiTokenStorageKey]; + if (apiToken) { + this.apiToken = apiToken; + } + + const selfHostedCodecovURL: string | undefined = + result[selfHostedCodecovURLStorageKey]; + if (selfHostedCodecovURL) { + this.apiUrl = selfHostedCodecovURL; + } + + const selfHostedGithubURL: string | undefined = + result[selfHostedGitHubURLStorageKey]; + if (selfHostedGithubURL) { + this.provider = providers.githubEnterprise; + } } - static async checkAuth(payload: any): Promise { - const { baseUrl, token } = payload; + async fetch(input: RequestInfo, init?: RequestInit): Promise { + // fetch wrapper that adds API token auth if necessary - const url = urlJoin(baseUrl, this.checkAuthPath); + if (this.apiToken) { + console.log("using api token"); + return fetch(input, { + ...init, + headers: { + Authorization: `Bearer ${this.apiToken}`, + ...init?.headers, + }, + }); + } - const response = await fetch(url, { - headers: { - Authorization: `bearer ${token}`, - }, - }); + console.log("using session cookie"); + + return fetch(input, init); + } + + async checkAuth(payload: any): Promise { + // This is for testing config on save, so don't use storage values + const { baseUrl, token, provider } = payload; + + const url = urlJoin(baseUrl, "/api/v2/", provider, "/"); + // Don't use this.fetch for checkAuth as we don't want any old token to + // sneak into this request. + + if (token) { + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + Referrer: "https://github.com/codecov/codecov-api", + }, + }); + return response.ok; + } + + const response = await fetch(url); return response.ok; } - static async fetchCommitReport(payload: any, referrer: string): Promise { - const { service, owner, repo, sha, branch, path, flag, component_id } = - payload; + async fetchCommitReport(payload: any, referrer: string): Promise { + await this.init(); + const { owner, repo, sha, branch, path, flag, component_id } = payload; const url = new URL( - `/api/v2/${service}/${owner}/repos/${repo}/report`, - this.baseUrl + `/api/v2/${this.provider}/${owner}/repos/${repo}/report`, + this.apiUrl ); const params = { path }; @@ -91,7 +100,7 @@ export class Codecov { ) ).toString(); - const response = await fetch(url.toString(), { + const response = await this.fetch(url.toString(), { headers: { Referrer: referrer, }, @@ -104,17 +113,18 @@ export class Codecov { }; } - static async fetchPRComparison(payload: any, referrer: string): Promise { - const { service, owner, repo, pullid } = payload; + async fetchPRComparison(payload: any, referrer: string): Promise { + await this.init(); + const { owner, repo, pullid } = payload; const url = new URL( - `/api/v2/${service}/${owner}/repos/${repo}/compare`, - this.baseUrl + `/api/v2/${this.provider}/${owner}/repos/${repo}/compare`, + this.apiUrl ); const params = { pullid }; url.search = new URLSearchParams(params).toString(); - const response = await fetch(url.toString(), { + const response = await this.fetch(url.toString(), { headers: { Referrer: referrer, }, @@ -127,15 +137,16 @@ export class Codecov { }; } - static async listFlags(payload: any, referrer: string): Promise { - const { service, owner, repo } = payload; + async listFlags(payload: any, referrer: string): Promise { + await this.init(); + const { owner, repo } = payload; const url = new URL( - `/api/v2/${service}/${owner}/repos/${repo}/flags`, - this.baseUrl + `/api/v2/${this.provider}/${owner}/repos/${repo}/flags`, + this.apiUrl ); - const response = await fetch(url.toString(), { + const response = await this.fetch(url.toString(), { headers: { Referrer: referrer, }, @@ -148,15 +159,16 @@ export class Codecov { }; } - static async listComponents(payload: any, referrer: string): Promise { - const { service, owner, repo } = payload; + async listComponents(payload: any, referrer: string): Promise { + await this.init(); + const { owner, repo } = payload; const url = new URL( - `/api/v2/${service}/${owner}/repos/${repo}/components`, - this.baseUrl + `/api/v2/${this.provider}/${owner}/repos/${repo}/components`, + this.apiUrl ); - const response = await fetch(url.toString(), { + const response = await this.fetch(url.toString(), { headers: { Referrer: referrer, }, @@ -169,5 +181,3 @@ export class Codecov { }; } } - -Codecov._init(); diff --git a/tailwind.config.js b/tailwind.config.js index 869fbe2..bc79642 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,15 +2,11 @@ module.exports = { content: ["./src/**/*.tsx"], theme: { - extend: {}, + extend: { + colors: { + "codecov-pink": "rgb(240, 31, 122)", + "codecov-blue": "rgb(0, 136, 233)", + }, + }, }, - plugins: [require('daisyui')], - daisyui: { - themes: [{ - codecov: { - ...require('daisyui/src/colors/themes')["[data-theme=light]"], - primary: '#FF0077', - } - }] - } -} +};