방금 푼 문제를 디스코드로! 백준코드 2.0 업데이트! Chrome Web Store, GitHub
BJCORD
백준코드(BJCORD)는 백준에서 문제를 풀면 디스코드 웹훅을 통해 누가 어떤 문제를 풀었는지 전달해주는 크롬 확장 프로그램입니다. 2024년 5월 5일에 개발을 시작하여 5월 30일에 처음으로 배포되었습니다. 오픈소스 프로젝트로 유지보수되어 2025년 9월 16일에 1.7.2 버전이 릴리즈 되었습니다.
백준코드 1버전은 그렇게 잘 계획되어 만들어진 프로젝트는 아니었습니다. HTML 파일 2개와 바닐라 JS 파일 5개 정도로 이루어진 작은 프로젝트였습니다. 1년 4개월 정도 되는 기간동안 10번 정도의 릴리즈가 있었습니다. 오픈소스 프로젝트 특성상 다른 분들이 이슈를 올리거나 PR을 보내주시는 경우도 많았습니다. 순수 JS로 작성된 프로젝트는 점점 유지보수하기 쉽지 않게 되었습니다. 하나의 파일에 몇 백줄의 코드가 작성되어 있었고, 타입은 JSDoc에 의존해서 추론하고 있었습니다. 다른 MIT 라이선스 오픈소스에서 가져온 코드와 제가 작성한 코드가 뒤섞여 있었습니다. 또, 바닐라 JS로 HTML을 조작하는 코드는 이해하기 어려웠습니다. 타입스크립트로 작성된 깔끔한 코드의 필요성을 느끼게 되었고 2025년 9월부터 10월까지 약 한 달간 백준코드 2.0을 개발하게 되었습니다.
BJCORD 2.0
백준코드 2.0은 오픈소스로 유지보수하기 쉽도록 설계되었습니다. wxt라는 cross-browser 확장 프로그램 프레임워크를 사용했습니다. 기존 백준코드는 JS로 작성된 프로젝트를 통째로 압축해 그대로 배포하는 방식으로 관리되었습니다. wxt는 빌드 시스템을 내장하고 있어 Node.js 환경에서 개발하고 빌드할 수 있습니다. 빌드 후 배포하기 때문에 확장 프로그램의 용량이 크게 줄어드는 것도 장점입니다. 다음은 제가 사용한 기술 스택입니다.
TS기반의 Vite + React를 사용해 뷰를 작성했습니다. 내부 로직은 BJCORD 1과의 호환성을 위해 기존과 동일하게 작성하되 JS를 TS로 변환하며 리팩토링했습니다. BJCORD 1과의 겉으로 보이는 기능은 차이점이 없지만 코드가 많이 개선되었습니다.
코드 개선
기존 BJCORD 1 코드는 하나의 파일에 많은 코드가 작성되어 있었습니다. 특히 대부분의 값이 하드코딩되어 있어 유지보수가 어려웠습니다. BJCORD 2에서는 코드를 여러 파일로 나누고 상수화하여 유지보수를 쉽게 했습니다.
export const GITHUB_URL = "https://github.com/BaekjoonCord/BJCORD-extension";
export const HOW_TO_USE_URL =
"https://github.com/BaekjoonCord/BJCORD-extension/blob/main/README.md";
export const CHROME_STORE_URL =
"https://chromewebstore.google.com/detail/%EB%B0%B1%EC%A4%80%EC%BD%94%EB%93%9C/ichhnkdadkmehpahpbdgcoeccfahgpdk";
export const WEBHOOK_KEY = "webhooks";
export const SHOW_EMOJI_KEY = "showEmoji";
export const SEND_FIRST_AC_ONLY_KEY = "webhookFirstAcceptOnly";
export const DEFAULT_SHOW_EMOJI = true;
export const DEFAULT_SEND_FIRST_AC_ONLY = false;
// ...
또, 기존 BJCORD 1 코드는 통일된 라이브러리가 없었습니다. sync storage에 접근하는 코드가 여러 부분에 반복되고 흩어져 있었습니다. BJCORD 2에서는 타입스크립트의 타입 시스템을 이용해 통일된 인터페이스를 제공하는 라이브러리를 만들 수 있었습니다.
export interface Webhook {
id: string;
name: string;
url: string;
displayName?: string;
active: boolean;
}
export const updateWebhook = async (
id: string,
updates: Partial<Omit<Webhook, "id">>
) => {
const webhooks = await getWebhooks();
const index = webhooks.findIndex((wh) => wh.id === id);
if (index === -1) {
throw new Error("Webhook not found");
}
webhooks[index] = {
...webhooks[index],
...updates,
};
await syncWebhooks(webhooks);
};
UI 개선
BJCORD 1의 UI와 크게 달라진 부분은 없지만 사소한 부분에서 개선이 있었습니다. popup 페이지에서 아무런 웹훅이 없는 경우 기존에는 웹훅이 없다는 문구만 표시되었지만, BJCORD 2에서는 웹훅을 추가할 수 있는 링크가 추가되었습니다.

사소한 차이이지만, 사용자가 확장 프로그램을 처음 설치했을 때 어떻게 해야 할지 모르는 경우를 줄일 수 있었습니다. 사용자의 입장에서 생각해보는 것이 중요하다는 것을 다시 한 번 느꼈습니다.
발생한 문제
BJCORD 2를 개발 완료하고 배포했습니다. 실제 배포 환경에서는 1버전의 데이터가 2버전으로 자동으로 이전되어야 했기 때문에 그 부분에서 조금 문제가 있었습니다.
권한 문제
BJCORD 1은 https://www.acmicpc.net/*
도메인에 접근할 수 있는 권한이 필요했습니다. BJCORD 2에서는 *://www.acmicpc.net/*
도메인에 대해 권한을 갖도록 설정이 되어있었습니다. 이 부분이 문제가 되었습니다. 업데이트가 된 후 권한이 확대된 것으로 인식되어 확장 프로그램이 비활성화되는 문제가 발생했습니다. 새벽 3시에 급하게 1버전으로 롤백하고 권한을 축소해 다시 배포했습니다.
데이터 이전 문제
BJCORD 1의 데이터를 다시 읽어올 때 데이터의 형태가 다르게 설정되어 있었습니다. BJCORD 1에서는 웹훅에 id가 없었지만 BJCORD 2에서는 id가 필수로 필요했습니다. 웹훅을 불러오기 전 id가 없는 웹훅이 있다면 id를 생성해주는 코드를 작성했습니다.
let hasUndefinedId = false;
for (const wh of webhooks) {
if (wh.id === undefined) {
hasUndefinedId = true;
break;
}
}
if (hasUndefinedId) {
for (const wh of webhooks) {
if (wh.id === undefined) {
wh.id = createUUID();
}
}
await syncWebhooks(webhooks);
}
또, BJCORD 2의 웹훅 활성화 상태는 active 필드로 관리되지만 BJCORD 1에서는 enabled 필드로 관리되었습니다. 이 부분은 제가 BJCORD 1의 데이터를 제대로 확인하지 못해 발생한 문제였습니다. JS로 복잡하게 작성되어 있었기 때문에 정확히 무슨 타입인지 파악하지 못했습니다. 이 부분도 급하게 핫픽스를 작성해 배포했습니다.
let hasEnabledField = false;
for (const wh of webhooks) {
if ((wh as any).enabled !== undefined) {
hasEnabledField = true;
break;
}
}
if (hasEnabledField) {
for (const wh of webhooks) {
if ((wh as any).enabled !== undefined) {
wh.active = (wh as any).enabled;
delete (wh as any).enabled;
}
}
await syncWebhooks(webhooks);
}
마치며
백준코드 2.0을 개발하며 오픈소스 프로젝트를 유지보수하기 쉽도록 설계하는 것이 얼마나 중요한지 다시 한 번 느꼈습니다. 코드가 깔끔하게 작성되어 있으면 다른 분들이 이슈를 올리거나 PR을 보내주시는 것도 더 쉬워집니다. 또, 어이없는 실수를 줄일 수 있습니다. 앞으로도 오픈소스 프로젝트를 유지보수하기 쉽도록 설계하는 것을 목표로 삼아야겠습니다.