Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .tours/--.tour
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "https://aka.ms/codetour-schema",
"title": "로그인 토큰 처리",
"steps": [
{
"file": "app/screens/index.tsx",
"description": "### 로그인 버튼 화면",
"line": 27
},
{
"file": "app/assets/util/login.ts",
"description": "### 소셜로그인 로직",
"line": 8
},
{
"file": "app/hooks/login/login.ts",
"description": "### 로그인 훅",
"line": 7
},
{
"file": "app/api/login.ts",
"description": "### 리프레쉬토큰API",
"line": 35
}
]
}
29 changes: 4 additions & 25 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { navigationRef } from './RootNavigation';
import SimpleSnackbarUI from '~/components/common/toast';
import { useApiError } from '~/hooks/useApiError';
import { AppStateComponent } from '~/observers/app-state';
// import { AxiosConfig } from './axiosConfig';
import { AxiosConfig } from './axiosConfig';
import { EventProvider } from 'react-native-outside-press';
import 'react-native-gesture-handler';
import axios, { AxiosResponse } from 'axios';
import { getStorage } from '~/assets/util/storage';
import { SERVER_URL, TOKEN_STORAGE_KEY } from '~/assets/util/constants';
import { getStorage, get } from '~/assets/util/storage';
import { SERVER_URL } from '~/assets/util/constants';
import { CustomResponse } from 'types/modules';
import * as encoding from 'text-encoding';
import { WebSocketConnector } from '~/components/chat/web-socket-connector';
Expand All @@ -33,28 +33,6 @@ if (__DEV__) {
);
}

axios.defaults.baseURL = SERVER_URL;

axios.interceptors.request.use(
async (config) => {
const token = await getStorage(TOKEN_STORAGE_KEY);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
},
);

axios.interceptors.response.use(
(response: AxiosResponse<CustomResponse<any>>) => {
console.log('@@ AXIOS RESPONSE 123', response);
return response.data.data;
},
);

const Stack = createNativeStackNavigator();

function RootNavigator() {
Expand All @@ -75,6 +53,7 @@ export default function App() {
<ThemeProvider theme={theme}>
<QueryClientProvider client={queryClient}>
<RecoilRoot>
<AxiosConfig />
<NavigationContainer ref={navigationRef}>
<WebSocketConnector />
<RootNavigator />
Expand Down
54 changes: 53 additions & 1 deletion app/api/login.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import axios from 'axios';
import AlertPopup from 'react-native-global-components/components/AlertPopup/AlertPopup';
import { Envelope, SocialLoginRoute } from 'types/common';
import { navigationRef } from '~/../RootNavigation';
import { NT_REFRESH_TOKEN } from '~/assets/util/constants';
import { clearStorage, get } from '~/assets/util/storage';

interface TokenParams {
id_token: string;
Expand All @@ -16,8 +21,55 @@ export const postLogin = async (
route: SocialLoginRoute,
params: TokenParams,
) => {
console.log(route, params);
console.log('[postLogin]', route, params);

// TODO: 사실 이 부분은 api가 통합되어야 할 듯 => 수정될 것 같음
return axios.post(`/login/${route}`, params);
};

// WIP 에러코드랑 토큰 보내느 형식 다시 맞춰야할듯
let cached: any = null;
let lastCall = 0;
const maxAge = 1000; // 캐시 유지 기간 (밀리초)

export const refreshTokenAPI = async () => {
// console.group('refreshTokenAPI 시작 =>');
// const now = Date.now();

// if (cached !== null && now - lastCall < maxAge) {
// return cached; // 캐시된 결과 반환
// }

// lastCall = now; // 호출 시간 업데이트

try {
const refresh_token = await AsyncStorage.getItem(NT_REFRESH_TOKEN);
console.log('refresh_token =>', refresh_token);

const response = await axios.post(
`/user/reToken`,
{}, // 요청 본문이 필요 없다면 빈 객체를 전달합니다.
{
headers: {
Authorization: `Bearer ${refresh_token}`,
},
},
);
console.log('response =>', response);

cached = response.data; // 응답 캐시

return cached;
} catch (error) {
cached = null; // 오류 발생 시 캐시 초기화

console.log('!!error', error);
// 리프레쉬 토큰도 만료되었다면?
// 리프레시 토큰 만료 처리 로직
if (error) {
// 리프레시 토큰도 만료됨으로써 로그인페이지로 리다이렉트 처리해야함
// AsyncStorage.clear();
navigationRef.current.navigate('RootScreen');
}
}
};
33 changes: 31 additions & 2 deletions app/api/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
import axios from 'axios';
import { userInfoType } from '~/types/user';
import { loadTokens } from '~/hooks/login/login';
import { TOKEN_ERROR_STATUS, userInfoType } from '~/types/user';

export const getUserInfoAPI = async () => {
return await axios.get('/user');
const { accessToken } = await loadTokens(); // 비동기적으로 토큰 로드

return await axios
.get('/user', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((res) => {
console.log('res', res);
return res;
})
.catch((err) => {
// console.error('err', err);
// // 토큰 관련 에러
// if (err.status == 401) {
// switch (err.message) {
// case TOKEN_ERROR_STATUS.ACCESS_TOKEN_EXPIRED:
// // 액세스 토큰 만료 시
// // user/reToken API에 리프레쉬 토큰을 담아 호출하여, 토큰 재발급 후 다시 요청하게끔.
// '';
// case TOKEN_ERROR_STATUS.REFRESH_TOKEN_EXPIRED:
// // 리프레쉬 토큰 만료 시
// // 액세스와 리프레쉬 토큰 둘 다 만료임으로, 로그인 화면으로 리다이렉트 시킨다.
// '';
// }
// }
// console.log('err', err);
});
};

export const patchUserInfoAPI = (form: FormData) =>
Expand Down
13 changes: 12 additions & 1 deletion app/assets/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,19 @@ export const sortOptions: SortType[] = [
{ nm: '가까운순', cd: 'distance' },
];

export const TOKEN_STORAGE_KEY = 'NT-AUTH-TOKEN';
export const NT_ACCESS_TOKEN = 'NT-ACCESS-TOKEN';
export const NT_REFRESH_TOKEN = 'NT-REFRESH-TOKEN';

const BASE_URL = 'nthing.kkookkss.synology.me';
export const SERVER_URL = `https://${BASE_URL}`;
export const WEBSOCKET_SERVER_URL = `wss://${BASE_URL}`;

export const naverLoginKeys = {
consumerKey: 'vnH89uX9Nczv8vOeXfQw', // 이거 필요한건가?
consumerSecret: 'TtWl5HamP7', // 얘도 필요한건가?
appName: 'nThing',
serviceUrlScheme: 'naverlogin', // only for iOS
};

export const googleWebClientId =
'141023294009-g5k49bh6cmk0re3c94mnu9esi4ep3gcc.apps.googleusercontent.com';
83 changes: 83 additions & 0 deletions app/assets/util/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import NaverLogin, { NaverLoginRequest } from '@react-native-seoul/naver-login';
import { postLogin } from '~/api/login';
import { SocialLoginRoute } from '~/types/common';
import * as KakaoLogin from '@react-native-seoul/kakao-login';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import {
googleWebClientId,
naverLoginKeys,
NT_ACCESS_TOKEN,
NT_REFRESH_TOKEN,
} from './constants';
import auth from '@react-native-firebase/auth';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { setToken } from '~/hooks/login/login';

interface loginResponseType {
data: { message: string; access_token: string; refresh_token: string };
message: null;
status: number;
code: null;
}

export const getServiceToken = async (
social: SocialLoginRoute,
idToken: string,
) => {
try {
console.log('getServiceToken ::', social, idToken);
const res: loginResponseType = await postLogin(social, {
id_token: idToken,
});

console.log('응답', res);
return res;
} catch (error) {
console.error('Token retrieval failed:', error);
throw error;
}
};

export const naverLogin = async () => {
const NAVER_LOGIN_KEY: NaverLoginRequest = naverLoginKeys;
try {
const { successResponse } = await NaverLogin.login(NAVER_LOGIN_KEY);
console.log('네이버 토큰', successResponse?.accessToken);
} catch (e) {
console.warn(e);
}
};

export const kakaoLogin = async () => {
try {
const { accessToken } = await KakaoLogin.login();
// console.log('카카오 토큰', accessToken);

const token = await getServiceToken('kakao', accessToken);
console.log('getServiceToken카카오 토큰', token);
setToken(token);

return token;
} catch (e) {
console.log('kakaoLogin Error', e);
}
};

export const googleLogin = async () => {
GoogleSignin.configure({
webClientId: googleWebClientId,
});
try {
const { idToken } = await GoogleSignin.signIn();
// console.log('구글 토큰', idToken);
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
const firebaseToken = await auth()
.signInWithCredential(googleCredential)
.then(({ user }) => user.getIdToken());
// console.log('새로 받은 토큰', firebaseToken);
const token = await getServiceToken('google', firebaseToken);
return token;
} catch (e) {
console.warn(e);
}
};
13 changes: 13 additions & 0 deletions app/assets/util/storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { NT_ACCESS_TOKEN } from './constants';

export const getStorage = async (key: string) => {
const result = await AsyncStorage.getItem(key);
Expand All @@ -14,3 +15,15 @@ export const removeStorage = async (key: string) => {
};

export const clearStorage = AsyncStorage.clear();
export const get = async () => {
try {
const savedToken = await AsyncStorage.getItem(NT_ACCESS_TOKEN);
const token = savedToken ? JSON.parse(savedToken) : null; // 저장된 값이 없을 경우 null 처리
console.log('get token', token);

return token;
} catch (error) {
console.log('Error getting token:', error);
return null; // 에러 발생 시 null 반환
}
};
6 changes: 3 additions & 3 deletions app/assets/util/web-socket.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Frame, messageCallbackType, Stomp, StompConfig } from '@stomp/stompjs';
import { TOKEN_STORAGE_KEY, WEBSOCKET_SERVER_URL } from 'assets/util/constants';
import { NT_ACCESS_TOKEN, WEBSOCKET_SERVER_URL } from 'assets/util/constants';
import { getStorage } from 'assets/util/storage';
import { ChatMessage, WebsocketMessageType } from 'types/chat';

Expand All @@ -12,7 +12,7 @@ export const connect = async (
roomIds: number[],
callback: messageCallbackType,
) => {
const token = await getStorage(TOKEN_STORAGE_KEY);
const token = await getStorage(NT_ACCESS_TOKEN);
// console.log('웹소켓에 보낼 token ' + token);
stompClient.connect({ Authorization: `Bearer ${token}` }, (frame) => {
console.log('hi connected', frame);
Expand All @@ -23,7 +23,7 @@ export const connect = async (
};

export const send = async (roomId: number, message: ChatMessage) => {
const token = await getStorage(TOKEN_STORAGE_KEY);
const token = await getStorage(NT_ACCESS_TOKEN);
stompClient.send(
`/send-to/room/${roomId}`,
{ Authorization: `Bearer ${token}` },
Expand Down
3 changes: 3 additions & 0 deletions app/hooks/chatting/chat-rooms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { getChatRoomsAPI } from 'api/chatting';
import { chatRoomsKeys } from 'key/chatting';

export function useChatRooms() {
const test = false;

const chatRooms = useQuery({
queryKey: chatRoomsKeys.info(),
queryFn: getChatRoomsAPI,
enabled: test,
});

return chatRooms;
Expand Down
Loading