@sayren/store-sdk 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +746 -17
- package/dist/index.mjs +421 -15
- package/package.json +1 -1
- package/src/client.ts +88 -2
- package/src/schemas/storefront-hosting.ts +525 -9
- package/src/session.ts +42 -1
package/src/session.ts
CHANGED
|
@@ -32,6 +32,9 @@ export const STORE_SESSION_KEYS = {
|
|
|
32
32
|
pkce: "sayren.store.pkce",
|
|
33
33
|
} as const;
|
|
34
34
|
|
|
35
|
+
/** 로그아웃 폐기 요청 제한 시간 */
|
|
36
|
+
const REVOKE_TIMEOUT_MS = 5_000;
|
|
37
|
+
|
|
35
38
|
/** Web Locks 이름 — 탭 간 refresh 회전 직렬화용 */
|
|
36
39
|
const REFRESH_LOCK = STORE_SESSION_KEYS.refresh;
|
|
37
40
|
|
|
@@ -68,8 +71,14 @@ export interface StoreSession {
|
|
|
68
71
|
* 회전이 실패(만료·재사용 폐기)하면 세션을 clear하고 false. (401 경로는 onUnauthorized→clear가 별도 처리.)
|
|
69
72
|
*/
|
|
70
73
|
refresh(): Promise<boolean>;
|
|
71
|
-
/** 세션/리프레시 키를 지우고 구독자에게 통지한다 */
|
|
74
|
+
/** 세션/리프레시 키를 지우고 구독자에게 통지한다(서버의 토큰은 그대로다 — 로그아웃은 `logout()`) */
|
|
72
75
|
clear(): void;
|
|
76
|
+
/**
|
|
77
|
+
* 로그아웃 — 서버에서 **refresh 토큰(패밀리째)과 access 토큰을 폐기**(`POST /oauth2/revoke`, RFC 7009)한 뒤 세션을 지운다.
|
|
78
|
+
* refresh를 폐기하지 않으면 쿠키만 지워도 서버의 refresh 패밀리가 절대 만료까지 살아 있어, 새어 나간 refresh 토큰으로
|
|
79
|
+
* 계속 새 userToken을 받을 수 있다. 폐기 요청 실패(네트워크·이미 무효)는 무시하고 세션은 항상 지운다.
|
|
80
|
+
*/
|
|
81
|
+
logout(): Promise<void>;
|
|
73
82
|
/** 변경 구독(useSyncExternalStore 소비용) — getToken을 스냅샷으로 쓴다. 해지 함수 반환 */
|
|
74
83
|
subscribe(cb: () => void): () => void;
|
|
75
84
|
/** 현재 userToken (getToken()과 동일 — 게터 형태 소비용) */
|
|
@@ -116,6 +125,37 @@ export function createStoreSession(options: StoreSessionOptions): StoreSession {
|
|
|
116
125
|
notify();
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
/** RFC 7009 — 무효 토큰이어도 200이다. 실패는 호출부가 무시한다. 응답이 늦어도 로그아웃이 멈추지 않게 5초로 끊는다 */
|
|
129
|
+
async function revokeToken(token: string): Promise<void> {
|
|
130
|
+
await fetchFn(`${baseUrl}/oauth2/revoke`, {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
133
|
+
body: new URLSearchParams({ token, client_id: clientId }).toString(),
|
|
134
|
+
// AbortSignal.timeout이 없는 브라우저는 제한 없이 보낸다(로그아웃은 그래도 진행된다)
|
|
135
|
+
signal:
|
|
136
|
+
typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function"
|
|
137
|
+
? AbortSignal.timeout(REVOKE_TIMEOUT_MS)
|
|
138
|
+
: undefined,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function logout(): Promise<void> {
|
|
143
|
+
// 진행 중인 갱신이 있으면 끝나기를 기다린다 — 그 사이 회전된 새 refresh 토큰을 폐기해야 한다(옛 토큰만 폐기하면 새 패밀리 토큰이 남는다)
|
|
144
|
+
if (refreshInFlight) await refreshInFlight.catch(() => false);
|
|
145
|
+
const refreshToken = storage.get(STORE_SESSION_KEYS.refresh);
|
|
146
|
+
const accessToken = getToken();
|
|
147
|
+
// refresh 먼저 — 패밀리를 폐기해야 새 userToken 발급이 끊긴다. access는 남은 수명 동안의 사용을 끊는다
|
|
148
|
+
for (const token of [refreshToken, accessToken]) {
|
|
149
|
+
if (!token) continue;
|
|
150
|
+
try {
|
|
151
|
+
await revokeToken(token);
|
|
152
|
+
} catch {
|
|
153
|
+
// 네트워크 실패여도 로그아웃은 진행한다
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
clear();
|
|
157
|
+
}
|
|
158
|
+
|
|
119
159
|
let refreshInFlight: Promise<boolean> | null = null;
|
|
120
160
|
|
|
121
161
|
function refresh(): Promise<boolean> {
|
|
@@ -235,6 +275,7 @@ export function createStoreSession(options: StoreSessionOptions): StoreSession {
|
|
|
235
275
|
setTokens,
|
|
236
276
|
refresh,
|
|
237
277
|
clear,
|
|
278
|
+
logout,
|
|
238
279
|
subscribe,
|
|
239
280
|
authorize,
|
|
240
281
|
handleCallback,
|