
항해99에서 의미있는 기부 캠페인을 한다는 소식을 인스타를 통해 우연히 접했다.


깃허브에서 잔디의 개수를 가져오고 그만큼 돈으로 환산하여 기부를 한다.

나는 그저 여기에서 깃허브 계정으로 로그인만 했을 뿐인데, 어떻게 내 잔디 개수를 읽을 수 있었는지 궁금했다.
처음에는 스크래핑을 의심하며 개발자 도구를 살펴봤고, GitHub API의 존재를 알게되었다.

우선 GitHub 로그인하고 기부하기 버튼을 누르면 이러한 깃허브 로그인 페이지가 뜬다.
https://github.com/login?client_id=Ov23lialZQGo2M0UxvyK&return_to=%2Flogin%2Foauth%2Fauthorize%3Fclient_id%3DOv23lialZQGo2M0UxvyK
이러한 url로 이동하는데 이때
client_id=Ov23lialZQGo2M0UxvyK 는 GitHub OAuth 앱 설정에서 생성된 클라이언트 ID이다.
return_to=/login/oauth/authorize?client_id=Ov23lialZQGo2M0UxvyK는 인증 후 GitHub가 리디렉션할 URL이다. 로그인을 하면 바로 이 url로 이동하고 이곳에서 code를 반환받고 곧바로 /campaign(우리가 아는 원래 이벤트 페이지)로 이동한다.
인증 후
return_to로 리디렉션되고
code 값을 빼낸 후
/campaign으로 리디렉션된다.
지금까지 이 과정은 code를 얻기 위함인데 이거는 GitHub OAuth 인증 과정에서 발급되는 임시 코드이다. 이 임시 코드는 애플리케이션이 GitHub로부터 액세스 토큰을 요청할 때만 사용된다. 찾아보니 이 임시코드는 한 번만 사용할 수 있고, 유효 기간이 아주 짧다고 한다.
클라이언트에서는 이 쿼리 속 code를 추출하여 백엔드로 보내고 백엔드에서는 GitHub API를 사용해서 내 계정의 잔디 개수를 알아내어 클라이언트로 반환한다.
useEffect(() => {
const code = router.query.code;
if (code && typeof code === 'string') {
handleGithubCallback(code, {
onSuccess: (data) => {
if (data) {
setTimeout(() => {
dynamicCampaignRef.current?.scrollIntoView({
behavior: 'smooth',
});
}, 100);
}
},
onError: (error) => {
console.error('GitHub OAuth Callback Error:', error);
},
});
}
}, [router.query.code, handleGithubCallback]);
이렇게 url 속 code 값을 추출하고 아래 코드를 보면 /v3/campaign/2024-christmas/contribute?code=${code} 여기를 통해 백엔드 api를 호출한다.
export async function githubCallback(code: string) {
try {
return await instanceV2.post(
`/v3/campaign/2024-christmas/contribute?code=${code}`,
);
} catch (error) {
console.error(error);
Sentry.captureException(error, {
tags: {
location: 'grass-campaign',
type: 'github-callback',
},
});
}
}
이렇게 호출되어 얻게된 data는 localStorage에 저장된다.
const GITHUB_DATA_KEY = 'github-user-data';
function setStorageData(data: GrassCampaignResponseDto) {
if (typeof window === 'undefined') {
return;
}
try {
const expiredAt = Date.now() + ONE_HOUR;
localStorage.setItem(GITHUB_DATA_KEY, JSON.stringify({ data, expiredAt }));
} catch (error) {
console.error('Failed to save data:', error);
localStorage.removeItem(GITHUB_DATA_KEY);
}
}
...
return useMutation({
mutationFn: githubCallback,
onSuccess: async (data) => {
...
setStorageData(data);
...
Application 탭의 Local storage에서 github-user-data을 확인해보면
currentContributionTotalCount: 722020
gitHubUserId: [gitHubUserId]
gitHubUsername: [gitHubUsername]
pixelCount: 8
startPixel: 3396
totalContributionsIn2024: 1415
이러한 형식으로 저장이 되어있는 것을 확인할 수 있다. 이때 이벤트에서 사용되는 내 잔디 개수는 totalContributionsIn2024 이 값이다.
GitHub API를 사용하는 코드는 백엔드에 있기 때문에 확인할 수는 없었지만 이렇게 잔디 정보를 가져와서 보여줄 수 있다는 것을 알 수 있었고, 다음에 내 포트폴리오에 활용해보면 좋을 거 같다.
