mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
6 Commits
openhands-
...
fix/reduce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48de69153c | ||
|
|
3599811c34 | ||
|
|
8c934b6c01 | ||
|
|
dd6817ea75 | ||
|
|
c266455a01 | ||
|
|
27ddf8c60c |
@@ -3,6 +3,13 @@
|
||||
* @param token The GitHub token
|
||||
* @returns The headers for the GitHub API
|
||||
*/
|
||||
/**
|
||||
* Given a GitHub token, retrieves the authenticated user
|
||||
* @param token The GitHub token
|
||||
* @returns The authenticated user or an error response
|
||||
*/
|
||||
import { authCache } from "#/utils/auth-cache";
|
||||
|
||||
const generateGitHubAPIHeaders = (token: string) =>
|
||||
({
|
||||
Accept: "application/vnd.github+json",
|
||||
@@ -103,14 +110,15 @@ export const retrieveAllGitHubUserRepositories = async (
|
||||
return repositories;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a GitHub token, retrieves the authenticated user
|
||||
* @param token The GitHub token
|
||||
* @returns The authenticated user or an error response
|
||||
*/
|
||||
export const retrieveGitHubUser = async (
|
||||
token: string,
|
||||
): Promise<GitHubUser | GitHubErrorReponse> => {
|
||||
// Check cache first
|
||||
const cachedUser = authCache.getGithubUser(token);
|
||||
if (cachedUser !== undefined) {
|
||||
return cachedUser;
|
||||
}
|
||||
|
||||
const response = await fetch("https://api.github.com/user", {
|
||||
headers: generateGitHubAPIHeaders(token),
|
||||
});
|
||||
@@ -124,6 +132,8 @@ export const retrieveGitHubUser = async (
|
||||
avatar_url: data.avatar_url,
|
||||
};
|
||||
|
||||
// Cache the successful response
|
||||
authCache.setGithubUser(token, user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -133,6 +143,8 @@ export const retrieveGitHubUser = async (
|
||||
status: response.status,
|
||||
};
|
||||
|
||||
// Cache the error response too
|
||||
authCache.setGithubUser(token, error);
|
||||
return error;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,22 +16,13 @@ import { retrieveAllGitHubUserRepositories } from "#/api/github";
|
||||
import store from "#/store";
|
||||
import { setInitialQuery } from "#/state/initial-query-slice";
|
||||
import { clientLoader as rootClientLoader } from "#/routes/_oh";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { generateGitHubAuthUrl } from "#/utils/generate-github-auth-url";
|
||||
import { GitHubRepositoriesSuggestionBox } from "#/components/github-repositories-suggestion-box";
|
||||
|
||||
export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
|
||||
let isSaas = false;
|
||||
let githubClientId: string | null = null;
|
||||
|
||||
try {
|
||||
const config = await OpenHands.getConfig();
|
||||
isSaas = config.APP_MODE === "saas";
|
||||
githubClientId = config.GITHUB_CLIENT_ID;
|
||||
} catch (error) {
|
||||
isSaas = false;
|
||||
githubClientId = null;
|
||||
}
|
||||
// Get config values from parent route
|
||||
const isSaas = window.__APP_MODE__ === "saas";
|
||||
const githubClientId = window.__GITHUB_CLIENT_ID__;
|
||||
|
||||
const ghToken = localStorage.getItem("ghToken");
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
@@ -28,6 +28,7 @@ import AllHandsLogo from "#/assets/branding/all-hands-logo.svg?react";
|
||||
import NewProjectIcon from "#/assets/new-project.svg?react";
|
||||
import DocsIcon from "#/assets/docs.svg?react";
|
||||
import { userIsAuthenticated } from "#/utils/user-is-authenticated";
|
||||
import { authCache } from "#/utils/auth-cache";
|
||||
import { generateGitHubAuthUrl } from "#/utils/generate-github-auth-url";
|
||||
import { WaitlistModal } from "#/components/waitlist-modal";
|
||||
import { AnalyticsConsentFormModal } from "#/components/analytics-consent-form-modal";
|
||||
@@ -35,6 +36,7 @@ import { setCurrentAgentState } from "#/state/agentSlice";
|
||||
import AgentState from "#/types/AgentState";
|
||||
|
||||
export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
|
||||
console.log('client loader');
|
||||
try {
|
||||
const config = await OpenHands.getConfig();
|
||||
window.__APP_MODE__ = config.APP_MODE;
|
||||
@@ -49,6 +51,17 @@ export const clientLoader = async ({ request }: ClientLoaderFunctionArgs) => {
|
||||
const analyticsConsent = localStorage.getItem("analytics-consent");
|
||||
const userConsents = analyticsConsent === "true";
|
||||
|
||||
// Store current tokens to detect changes
|
||||
const prevToken = (window as any).__PREV_TOKEN__;
|
||||
const prevGhToken = (window as any).__PREV_GH_TOKEN__;
|
||||
|
||||
// Clear cache if tokens changed
|
||||
if (token !== prevToken || ghToken !== prevGhToken) {
|
||||
(window as any).__PREV_TOKEN__ = token;
|
||||
(window as any).__PREV_GH_TOKEN__ = ghToken;
|
||||
authCache.clear();
|
||||
}
|
||||
|
||||
if (!userConsents) {
|
||||
posthog.opt_out_capturing();
|
||||
} else {
|
||||
|
||||
41
frontend/src/types/github.ts
Normal file
41
frontend/src/types/github.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export interface GitHubUser {
|
||||
id: number;
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
}
|
||||
|
||||
export interface GitHubErrorReponse {
|
||||
message: string;
|
||||
documentation_url: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface GitHubRepository {
|
||||
id: number;
|
||||
name: string;
|
||||
full_name: string;
|
||||
private: boolean;
|
||||
html_url: string;
|
||||
description: string | null;
|
||||
fork: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
pushed_at: string;
|
||||
git_url: string;
|
||||
ssh_url: string;
|
||||
clone_url: string;
|
||||
default_branch: string;
|
||||
}
|
||||
|
||||
export interface GitHubCommit {
|
||||
sha: string;
|
||||
commit: {
|
||||
author: {
|
||||
name: string;
|
||||
email: string;
|
||||
date: string;
|
||||
};
|
||||
message: string;
|
||||
};
|
||||
html_url: string;
|
||||
}
|
||||
8
frontend/src/types/global.d.ts
vendored
Normal file
8
frontend/src/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
__APP_MODE__: "saas" | "oss";
|
||||
__GITHUB_CLIENT_ID__: string | null;
|
||||
__PREV_TOKEN__?: string | null;
|
||||
__PREV_GH_TOKEN__?: string | null;
|
||||
}
|
||||
}
|
||||
81
frontend/src/utils/auth-cache.ts
Normal file
81
frontend/src/utils/auth-cache.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { GitHubUser, GitHubErrorReponse } from "#/types/github";
|
||||
|
||||
interface CacheEntry<T> {
|
||||
value: T;
|
||||
timestamp: number;
|
||||
token: string;
|
||||
}
|
||||
|
||||
type GitHubUserResponse = GitHubUser | GitHubErrorReponse;
|
||||
|
||||
class AuthCache {
|
||||
private static instance: AuthCache;
|
||||
|
||||
private cache: {
|
||||
isAuthed?: CacheEntry<boolean>;
|
||||
githubUser?: CacheEntry<GitHubUserResponse>;
|
||||
} = {};
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): AuthCache {
|
||||
if (!AuthCache.instance) {
|
||||
AuthCache.instance = new AuthCache();
|
||||
}
|
||||
return AuthCache.instance;
|
||||
}
|
||||
|
||||
private isExpired<T>(entry: CacheEntry<T>, maxAge: number): boolean {
|
||||
return Date.now() - entry.timestamp > maxAge;
|
||||
}
|
||||
|
||||
private tokenChanged<T>(entry: CacheEntry<T>, currentToken: string): boolean {
|
||||
return entry.token !== currentToken;
|
||||
}
|
||||
|
||||
getAuthStatus(token: string): boolean | undefined {
|
||||
const entry = this.cache.isAuthed;
|
||||
if (
|
||||
!entry ||
|
||||
this.isExpired(entry, 60000) ||
|
||||
this.tokenChanged(entry, token)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
setAuthStatus(token: string, value: boolean): void {
|
||||
this.cache.isAuthed = {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
getGithubUser(token: string): GitHubUserResponse | undefined {
|
||||
const entry = this.cache.githubUser;
|
||||
if (
|
||||
!entry ||
|
||||
this.isExpired(entry, 300000) ||
|
||||
this.tokenChanged(entry, token)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
setGithubUser(token: string, value: GitHubUserResponse): void {
|
||||
this.cache.githubUser = {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.cache = {};
|
||||
}
|
||||
}
|
||||
|
||||
export const authCache = AuthCache.getInstance();
|
||||
@@ -1,12 +1,24 @@
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { authCache } from "./auth-cache";
|
||||
|
||||
export const userIsAuthenticated = async () => {
|
||||
if (window.__APP_MODE__ === "oss") return true;
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return false;
|
||||
|
||||
// Check cache first
|
||||
const cachedStatus = authCache.getAuthStatus(token);
|
||||
if (cachedStatus !== undefined) {
|
||||
return cachedStatus;
|
||||
}
|
||||
|
||||
try {
|
||||
await OpenHands.authenticate();
|
||||
authCache.setAuthStatus(token, true);
|
||||
return true;
|
||||
} catch (error) {
|
||||
authCache.setAuthStatus(token, false);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user