Skip to content

Commit

Permalink
giscus 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
chlwlstlf committed Nov 21, 2024
1 parent cf8d7ef commit 07757c2
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 31 deletions.
14 changes: 7 additions & 7 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ masthead_title: # overrides the website title displayed in the masthead, use " "
breadcrumbs: true # true, false (default)
words_per_minute: 200
comments:
provider: "disqus" # false (default), "disqus", "discourse", "facebook", "staticman", "staticman_v2", "utterances", "giscus", "custom"
provider: "giscus" # false (default), "disqus", "discourse", "facebook", "staticman", "staticman_v2", "utterances", "giscus", "custom"
disqus:
shortname: "chlwlstlf"
discourse:
Expand All @@ -44,12 +44,12 @@ comments:
theme: # "github-light" (default), "github-dark"
issue_term: # "pathname" (default)
giscus:
repo_id: # Shown during giscus setup at https://giscus.app
category_name: # Full text name of the category
category_id: # Shown during giscus setup at https://giscus.app
discussion_term: # "pathname" (default), "url", "title", "og:title"
reactions_enabled: # '1' for enabled (default), '0' for disabled
theme: # "light" (default), "dark", "dark_dimmed", "transparent_dark", "preferred_color_scheme"
repo_id: "R_kgDOG8MDQA"
category_name: "Announcements"
category_id: "DIC_kwDOG8MDQM4CkeZa"
discussion_term: "pathname"
reactions_enabled: "1"
theme: "preferred_color_scheme"
staticman:
branch: # "master"
endpoint: # "https://{your Staticman v3 API}/v3/entry/github/"
Expand Down
41 changes: 17 additions & 24 deletions _includes/comments-providers/giscus.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
<script>
'use strict';

(function () {
var commentContainer = document.querySelector('#giscus-comments');

if (!commentContainer) {
return;
}

var script = document.createElement('script');
script.setAttribute('src', 'https://giscus.app/client.js');
script.setAttribute('data-repo', '{{ site.repository | downcase }}');
script.setAttribute('data-repo-id', '{{ site.comments.giscus.repo_id }}');
script.setAttribute('data-category', '{{ site.comments.giscus.category_name }}');
script.setAttribute('data-category-id', '{{ site.comments.giscus.category_id }}');
script.setAttribute('data-mapping', '{{ site.comments.giscus.discussion_term | default: "pathname" }}');
script.setAttribute('data-reactions-enabled', '{{ site.comments.giscus.reactions_enabled | default: 1 }}');
script.setAttribute('data-theme', '{{ site.comments.giscus.theme | default: "light" }}');
script.setAttribute('crossorigin', 'anonymous');

commentContainer.appendChild(script);
})();
</script>
<script
src="https://giscus.app/client.js"
data-repo="chlwlstlf/chlwlstlf.github.io"
data-repo-id="R_kgDOG8MDQA"
data-category="Announcements"
data-category-id="DIC_kwDOG8MDQM4CkeZa"
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="top"
data-theme="preferred_color_scheme"
data-lang="ko"
data-loading="lazy"
crossorigin="anonymous"
async
></script>
206 changes: 206 additions & 0 deletions _posts/2024-08-11-woowacourse-level3-refresh-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,209 @@ toc_sticky: true

# Refresh Token으로 Access Token 갱신하기

## apiClient.ts 코드

```ts
import { serverUrl } from "@/config/serverUrl";
import MESSAGES from "@/constants/message";

type Method = "GET" | "POST" | "PATCH" | "DELETE";

interface ApiProps {
endpoint: string;
headers?: Record<string, string>;
body?: object | null;
errorMessage?: string;
}

interface RequestProps extends ApiProps {
method: Method;
}

const createRequestInit = (
method: Method,
headers: Record<string, string>,
body: object | null
): RequestInit => {
const token = localStorage.getItem("accessToken");

return {
method,
headers: {
...headers,
Authorization: token ? `Bearer ${token}` : "",
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : null,
};
};

const fetchWithErrorHandling = async (
endpoint: string,
requestInit: RequestInit,
errorMessage: string = ""
) => {
// 오프라인 확인
if (!navigator.onLine) {
throw new Error(MESSAGES.ERROR.OFFLINE);
}

const response = await fetch(`${serverUrl}${endpoint}`, requestInit);

if (!response.ok) {
throw new Error(
errorMessage || `Error: ${response.status} ${response.statusText}`
);
}

const text = await response.text();

return text ? JSON.parse(text) : response;
};

const apiClient = {
get: ({ endpoint, headers = {}, errorMessage = "" }: ApiProps) => {
return apiClient.request({
method: "GET",
endpoint,
headers,
errorMessage,
});
},
post: ({
endpoint,
headers = {},
body = {},
errorMessage = "",
}: ApiProps) => {
return apiClient.request({
method: "POST",
endpoint,
headers,
body,
errorMessage,
});
},
patch: ({
endpoint,
headers = {},
body = {},
errorMessage = "",
}: ApiProps) => {
return apiClient.request({
method: "PATCH",
endpoint,
headers,
body,
errorMessage,
});
},
delete: ({ endpoint, headers = {}, errorMessage = "" }: ApiProps) => {
return apiClient.request({
method: "DELETE",
endpoint,
headers,
errorMessage,
});
},
request: async ({
method,
endpoint,
headers = {},
body = null,
errorMessage = "",
}: RequestProps) => {
const requestInit = createRequestInit(method, headers, body);
return await fetchWithErrorHandling(endpoint, requestInit, errorMessage);
},
};

export default apiClient;
```

## 잘못된 로직

동시 요청 충돌

여러 API 요청이 동시에 실행되고, 이 요청들이 모두 401 상태를 반환하는 경우 각각이 refreshToken을 이용해 별도로 갱신 요청을 보냅니다.
이로 인해 불필요한 갱신 요청이 다수 발생할 수 있고, 경우에 따라 서버에서 갱신 토큰을 무효화하거나 속도 제한(rate limit)에 걸릴 수 있습니다.

새로 갱신된 토큰의 경쟁 조건

한 요청에서 새로 갱신된 토큰이 저장되기 전에 다른 요청에서 여전히 만료된 토큰을 사용하게 되는 경우가 발생할 수 있습니다. 이는 클라이언트에서 갱신된 토큰 관리가 제대로 이루어지지 않음을 나타냅니다.

```ts
const refreshAccessToken = async (): Promise<string | undefined> => {
const refreshToken = localStorage.getItem("refreshToken");

const response = await fetch(`${serverUrl}${API_ENDPOINTS.REFRESH}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ refreshToken }),
});

const text = await response.text();
const data = text ? JSON.parse(text) : null;

const newAccessToken = response.headers.get("Authorization");

if (!response.ok) {
if (response.status === 401) {
new AuthorizationError(data.message || MESSAGES.ERROR.POST_REFRESH);
alert("토큰이 만료되었습니다. 다시 로그인 해주세요!");
localStorage.clear();
window.location.href = "/";
} else {
throw new HTTPError(data.message || MESSAGES.ERROR.POST_REFRESH);
}
} else if (newAccessToken) {
localStorage.setItem("accessToken", newAccessToken);

return newAccessToken;
}
};

const fetchWithToken = async (
endpoint: string,
requestInit: RequestInit,
errorMessage: string = ""
) => {
if (!navigator.onLine) {
throw new HTTPError(MESSAGES.ERROR.OFFLINE);
}

let response = await fetch(`${serverUrl}${endpoint}`, requestInit);
let text = await response.text();
let data = text ? JSON.parse(text) : null;

if (response.status === 401 && data.message === "토큰이 만료되었습니다.") {
const newAccessToken = await refreshAccessToken();
requestInit.headers = {
...requestInit.headers,
Authorization: `Bearer ${newAccessToken}`,
};

response = await fetch(`${serverUrl}${endpoint}`, requestInit);
text = await response.text();
data = text ? JSON.parse(text) : null;

if (!response.ok && response.status !== 401) {
throw new HTTPError(data.message || MESSAGES.ERROR.POST_REFRESH);
}
}

if (!response.ok && response.status !== 401) {
throw new HTTPError(data.message || errorMessage);
}

return text ? data : response;
};
```

## 개선된 설계의 핵심

단일 갱신 로직

하나의 갱신 작업만 실행되도록 설정하고, 갱신 중인 작업이 완료될 때까지 대기하도록 로직을 개선해야 합니다. 이미 갱신 중이라면 추가로 갱신 요청을 보내지 않도록 해야 합니다. 이를 위해 isRefreshing 플래그와 **failedQueue**를 사용합니다.

0 comments on commit 07757c2

Please sign in to comment.