Skip to content

Commit 6feff0d

Browse files
committed
feat: test
1 parent b23cf61 commit 6feff0d

File tree

6 files changed

+101
-29
lines changed

6 files changed

+101
-29
lines changed

apps/tuk-web/src/app/gathering/[gatheringId]/invites/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use client';
2-
31
import GatheringInviteList from '@/app/gathering/[gatheringId]/invites/src/components/GatheringInviteList';
42

53
export default function GatheringInviteListPage() {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
5+
export default function ClientOnly({ children }: { children: React.ReactNode }) {
6+
const [mounted, setMounted] = useState(false);
7+
8+
useEffect(() => setMounted(true), []);
9+
10+
if (!mounted) return null;
11+
12+
return children;
13+
}

apps/tuk-web/src/app/gathering/[gatheringId]/invites/src/components/SendInviteList.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { QueryErrorResetBoundary } from '@tanstack/react-query';
22
import { Suspense } from 'react';
33
import { ErrorBoundary } from 'react-error-boundary';
44

5+
import ClientOnly from '@/app/gathering/[gatheringId]/invites/src/components/ClientOnly';
56
import InvitListSkeleton from '@/app/gathering/[gatheringId]/invites/src/components/InvitListSkeleton';
67
import SendInviteListContent from '@/app/gathering/[gatheringId]/invites/src/components/SendInviteListContent';
78

@@ -10,9 +11,11 @@ const SendInviteList = () => {
1011
<QueryErrorResetBoundary>
1112
{({ reset }) => (
1213
<ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
13-
<Suspense fallback={<InvitListSkeleton />}>
14-
<SendInviteListContent />
15-
</Suspense>
14+
<ClientOnly>
15+
<Suspense fallback={<InvitListSkeleton />}>
16+
<SendInviteListContent />
17+
</Suspense>
18+
</ClientOnly>
1619
</ErrorBoundary>
1720
)}
1821
</QueryErrorResetBoundary>
Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,71 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22

3+
// export function waitForNativeToken(timeoutMs = 3000): Promise<string> {
4+
// return new Promise((resolve, reject) => {
5+
// const onToken = (e: Event) => {
6+
// const token = (e as CustomEvent).detail?.token ?? sessionStorage.getItem('accessToken');
7+
// cleanup();
8+
// if (token) resolve(token);
9+
// else reject(new Error('Token event received but empty'));
10+
// };
11+
// const onTimeout = () => {
12+
// cleanup();
13+
// reject(new Error('Token refresh timeout'));
14+
// };
15+
// const cleanup = () => {
16+
// window.removeEventListener('native-token-refreshed', onToken as any);
17+
// clearTimeout(timer);
18+
// };
19+
20+
// window.addEventListener('native-token-refreshed', onToken as any, { once: true });
21+
22+
// const existing = sessionStorage.getItem('accessToken');
23+
// if (existing) {
24+
// cleanup();
25+
// resolve(existing);
26+
// return;
27+
// }
28+
29+
// const timer = setTimeout(onTimeout, timeoutMs);
30+
// });
31+
// }
32+
333
export function waitForNativeToken(timeoutMs = 10000): Promise<string> {
434
return new Promise((resolve, reject) => {
5-
const onToken = (e: Event) => {
6-
const token = (e as CustomEvent).detail?.token ?? sessionStorage.getItem('accessToken');
35+
let done = false;
36+
const finish = (tok?: string, err?: Error) => {
37+
if (done) return;
38+
done = true;
739
cleanup();
8-
if (token) resolve(token);
9-
else reject(new Error('Token event received but empty'));
40+
if (tok) resolve(tok);
41+
else reject(err ?? new Error('Token refresh timeout'));
1042
};
11-
const onTimeout = () => {
12-
cleanup();
13-
reject(new Error('Token refresh timeout'));
43+
44+
const onCustom = (e: Event) => {
45+
const token = (e as CustomEvent).detail?.token ?? sessionStorage.getItem('accessToken');
46+
if (token) finish(token);
1447
};
48+
49+
const onPoll = () => {
50+
const t = sessionStorage.getItem('accessToken');
51+
if (t) finish(t);
52+
};
53+
1554
const cleanup = () => {
16-
window.removeEventListener('native-token-refreshed', onToken as any);
55+
window.removeEventListener('native-token-refreshed', onCustom as any);
56+
clearInterval(pollId);
1757
clearTimeout(timer);
1858
};
1959

20-
window.addEventListener('native-token-refreshed', onToken as any, { once: true });
21-
60+
// 즉시 체크
2261
const existing = sessionStorage.getItem('accessToken');
23-
if (existing) {
24-
cleanup();
25-
resolve(existing);
26-
return;
27-
}
62+
if (existing) return finish(existing);
63+
64+
window.addEventListener('native-token-refreshed', onCustom as any, { once: true });
65+
66+
// 폴링(짧고 가벼움)
67+
const pollId = setInterval(onPoll, 100);
2868

29-
const timer = setTimeout(onTimeout, timeoutMs);
69+
const timer = setTimeout(() => finish(undefined), timeoutMs);
3070
});
3171
}

apps/tuk-web/src/shared/lib/api/rest/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ const defaultJsonInstance = (baseURL: string) =>
1717
bridgeSend: msg => getBridgeSender()?.(msg),
1818
});
1919

20+
// const defaultJsonInstance = (baseURL: string) =>
21+
// new RestAPIInstance(baseURL, {
22+
// withCredentials: false,
23+
// authHeader: () => {
24+
// if (typeof window === 'undefined') return null;
25+
// const token =
26+
// 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIzMiIsIm1lbWJlcklkIjoiMzIiLCJ0b2tlblR5cGUiOiJBQ0NFU1MiLCJpYXQiOjE3NTUwMDM4NTQsImV4cCI6MTc1NzU5NTg1NH0.sXHpNsim8-nIUdCjnAcOIZGsg2gUo8rnpkBmN-z5dKU';
27+
// return token ? { Authorization: `Bearer ${token.trim()}` } : null;
28+
// },
29+
// bridgeSend: msg => getBridgeSender()?.(msg),
30+
// });
31+
2032
const formInstance = (baseURL: string) =>
2133
new RestAPIInstance(baseURL, {
2234
withCredentials: false,

apps/tuk-web/src/shared/lib/api/rest/rest.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,14 @@ export class RestAPI implements RestAPIProtocol {
114114
signal: signal ?? controller.signal,
115115
};
116116

117+
const isFormData = typeof FormData !== 'undefined' && data instanceof FormData;
118+
117119
if (!isGetLike && data !== undefined) {
118-
const isForm = hdr['Content-Type'] === 'multipart/form-data';
119-
if (isForm) {
120+
if (isFormData) {
120121
delete hdr['Content-Type'];
121122
reqInit.body = data;
122123
} else {
123-
if (!hdr['Content-Type']) hdr['Content-Type'] = 'application/json';
124+
hdr['Content-Type'] ||= 'application/json';
124125
reqInit.body = JSON.stringify(data);
125126
}
126127
}
@@ -146,33 +147,38 @@ export class RestAPI implements RestAPIProtocol {
146147
// 네이티브에 갱신 요청 (중복이면 기존 대기 재사용)
147148
await requestTokenRefresh(
148149
() => bridge({ type: AppBridgeMessageType.REQUEST_TOKEN_REFRESH, payload: '' }),
149-
10000 // 타임아웃 10초
150+
3000 // 타임아웃 10초
150151
);
151152

152153
// 새 토큰으로 헤더 갱신 후 1회 재시도
153-
const refreshedHdr = {
154-
...hdr,
155-
...(this.instance.getAuthHeader() ?? {}),
156-
};
154+
const refreshedHdr = { ...hdr };
155+
delete refreshedHdr['Authorization'];
156+
Object.assign(refreshedHdr, this.instance.getAuthHeader() ?? {});
157+
157158
const retry = await this.instance.request(fullUrl, {
158159
...reqInit,
159160
headers: refreshedHdr,
160161
});
162+
161163
if (!retry.ok) {
162164
const text = await retry.text().catch(() => '');
165+
163166
throw new APIError(text || retry.statusText, {
164167
status: retry.status,
165168
meta: { errorType: 'HTTP_ERROR', errorMessage: text || retry.statusText },
166169
url: fullUrl,
167170
method: methodUpper,
168171
});
169172
}
173+
170174
const retryBody = await readBody(retry, parseAs);
175+
171176
const isEnvelope2 =
172177
retryBody &&
173178
typeof retryBody === 'object' &&
174179
'success' in retryBody &&
175180
'data' in retryBody;
181+
176182
const payload2 = isEnvelope2 ? (retryBody as any).data : retryBody;
177183

178184
if (validate) {

0 commit comments

Comments
 (0)