Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: rate limit modal and auto login #22

Merged
merged 15 commits into from
May 5, 2024
Merged
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
SUPABASE_URL=
SUPABASE_ANON_KEY=
SUPABASE_ANON_KEY=
64 changes: 56 additions & 8 deletions cypress/e2e/devpool.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,62 @@ describe("DevPool", () => {
cy.get('div[id="issues-container"]').children().should("have.length", 2);
});

it("Display a message on rate limited", () => {
cy.intercept("https://api.github.com/repos/*/*/issues**", (req) => {
req.reply({
statusCode: 403,
});
}).as("getIssues");
cy.visit("/");
cy.get(".preview-header").should("exist");
describe("Display message on rate limited", () => {
it("Should display retry timeframe and login request with no tasks and no user", () => {
cy.intercept("https://api.github.com/user", (req) => {
req.reply({
statusCode: 403,
body: {},
});
}).as("getUser");
cy.intercept("https://api.github.com/repos/*/*/issues**", (req) => {
req.reply({
statusCode: 403,
});
}).as("getIssues");

cy.visit("/");
cy.get(".preview-header").should("exist");
cy.get(".preview-body-inner").should("include.text", "Please log in to GitHub to increase your GitHub API limits, otherwise you can try again at");
});

it("Should display retry timeframe with no tasks loaded and a logged in user", () => {
cy.intercept("https://api.github.com/user", (req) => {
req.reply({
statusCode: 200,
body: {},
});
}).as("getUser");
cy.intercept("https://api.github.com/repos/*/*/issues**", (req) => {
req.reply({
statusCode: 403,
});
}).as("getIssues");

cy.visit("/");
cy.get(".preview-header").should("exist");
cy.get(".preview-body-inner").should("include.text", "You have been rate limited. Please try again at");
});

it("Should display a hard one-hour retry timeframe with no auth token available", () => {
const urlParams = `#error=server_error&error_code=500&error_description=Error getting user profile from external provider`;

cy.intercept("https://api.github.com/user", (req) => {
req.reply({
statusCode: 403,
body: {},
});
}).as("getUser");
cy.intercept("https://api.github.com/repos/*/*/issues**", (req) => {
req.reply({
statusCode: 403,
});
}).as("getIssues");

cy.visit(`/${urlParams}`);
cy.get(".preview-header").should("exist");
cy.get(".preview-body-inner").should("include.text", "Your access token may have reached it's rate limit, please try again after one hour.");
});
});

it("Items can be sorted", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/home/fetch-github/fetch-and-display-previews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export async function fetchAvatars() {
const urlPattern = /https:\/\/github\.com\/(?<org>[^/]+)\/(?<repo>[^/]+)\/issues\/(?<issue_number>\d+)/;

const avatarPromises = cachedTasks.map(async (task) => {
const match = task.preview.body.match(urlPattern);
const match = task.preview.body?.match(urlPattern);
const orgName = match?.groups?.org;
if (orgName) {
return fetchAvatar(orgName);
Expand Down
28 changes: 14 additions & 14 deletions src/home/fetch-github/fetch-issues-preview.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Octokit } from "@octokit/rest";
import { getGitHubAccessToken, getGitHubUserName } from "../getters/get-github-access-token";
import { GitHubIssue } from "../github-types";
import { taskManager } from "../home";
import { displayPopupMessage } from "../rendering/display-popup-modal";
import { TaskNoFull } from "./preview-to-full-mapping";
import { getGitHubUser } from "../getters/get-github-user";

async function checkPrivateRepoAccess(): Promise<boolean> {
const octokit = new Octokit({ auth: await getGitHubAccessToken() });
Expand Down Expand Up @@ -39,6 +39,7 @@ async function checkPrivateRepoAccess(): Promise<boolean> {

export async function fetchIssuePreviews(): Promise<TaskNoFull[]> {
const octokit = new Octokit({ auth: await getGitHubAccessToken() });
const user = await getGitHubUser();

let freshIssues: GitHubIssue[] = [];
let hasPrivateRepoAccess = false; // Flag to track access to the private repository
Expand Down Expand Up @@ -79,12 +80,16 @@ export async function fetchIssuePreviews(): Promise<TaskNoFull[]> {
} catch (error) {
if (403 === error.status) {
console.error(`GitHub API rate limit exceeded.`);
if (taskManager.getTasks().length == 0) {
// automatically login if there are no issues loaded
automaticLogin(error);
const resetTime = error.response.headers["x-ratelimit-reset"];
const resetParsed = new Date(resetTime * 1000).toLocaleTimeString();

if (!user || user === null) {
Keyrxng marked this conversation as resolved.
Show resolved Hide resolved
rateLimitModal(
`You have been rate limited. Please log in to GitHub to increase your GitHub API limits, otherwise you can try again at ${resetParsed}.`
);
} else {
rateLimitModal(`You have been rate limited. Please try again at ${resetParsed}.`);
}
} else {
console.error(`Failed to fetch issue previews: ${error}`);
}
}

Expand All @@ -97,12 +102,7 @@ export async function fetchIssuePreviews(): Promise<TaskNoFull[]> {

return tasks;
}
function automaticLogin(error: unknown) {
const resetTime = error.response.headers["x-ratelimit-reset"];
const resetParsed = new Date(resetTime * 1000).toLocaleTimeString();

displayPopupMessage(
`GitHub API rate limit exceeded.`,
`You have been rate limited. Please log in to GitHub to increase your GitHub API limits, otherwise you can try again at ${resetParsed}.`
);

function rateLimitModal(message: string) {
displayPopupMessage(`GitHub API rate limit exceeded.`, message);
}
42 changes: 32 additions & 10 deletions src/home/getters/get-github-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import { Octokit } from "@octokit/rest";
import { GitHubUser, GitHubUserResponse } from "../github-types";
import { OAuthToken } from "./get-github-access-token";
import { getLocalStore } from "./get-local-store";
import { displayPopupMessage } from "../rendering/display-popup-modal";
declare const SUPABASE_STORAGE_KEY: string; // @DEV: passed in at build time check build/esbuild-build.ts

export async function getGitHubUser(): Promise<GitHubUser | null> {
const activeSessionToken = await getSessionToken();
if (activeSessionToken) {
return getNewGitHubUser(activeSessionToken);
} else {
return null;
}
return getNewGitHubUser(activeSessionToken);
}

async function getSessionToken(): Promise<string | null> {
Expand All @@ -30,13 +27,38 @@ async function getNewSessionToken(): Promise<string | null> {
const params = new URLSearchParams(hash.substr(1)); // remove the '#' and parse
const providerToken = params.get("provider_token");
if (!providerToken) {
return null;
const err = params.get("error");
const code = params.get("error_code");
const desc = params.get("error_description");

if (err === "server_error") {
if (code === "500") {
if (desc === "Error getting user profile from external provider") {
Keyrxng marked this conversation as resolved.
Show resolved Hide resolved
// without a token we can't get a dynamic retry timeframe
displayPopupMessage(`GitHub Login Provider`, `Your access token may have reached it's rate limit, please try again after one hour.`);
console.error("GitHub login provider");
}
}
}
}
return providerToken;
return providerToken || null;
}

async function getNewGitHubUser(providerToken: string): Promise<GitHubUser> {
async function getNewGitHubUser(providerToken: string | null): Promise<GitHubUser | null> {
const octokit = new Octokit({ auth: providerToken });
const response = (await octokit.request("GET /user")) as GitHubUserResponse;
return response.data;
try {
const response = (await octokit.request("GET /user")) as GitHubUserResponse;
0x4007 marked this conversation as resolved.
Show resolved Hide resolved
return response.data;
} catch (error: unknown) {
if (error.status === 403 && error.message.includes("API rate limit exceeded")) {
Keyrxng marked this conversation as resolved.
Show resolved Hide resolved
const resetTime = error.response.headers["x-ratelimit-reset"];
const resetParsed = new Date(resetTime * 1000).toLocaleTimeString();

displayPopupMessage(
"GitHub API rate limit exceeded",
`You have been rate limited. Please log in to GitHub to increase your GitHub API limits, otherwise you can try again at ${resetParsed}.`
);
}
}
return null;
}
2 changes: 1 addition & 1 deletion src/home/rendering/render-github-login-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function checkSupabaseSession() {
return session;
}

async function gitHubLoginButtonHandler() {
export async function gitHubLoginButtonHandler() {
Keyrxng marked this conversation as resolved.
Show resolved Hide resolved
const { error } = await supabase.auth.signInWithOAuth({
provider: "github",
options: {
Expand Down