Skip to content

Commit 5f85168

Browse files
committed
feat: Refresh
1 parent 5a299f5 commit 5f85168

File tree

8 files changed

+210
-70
lines changed

8 files changed

+210
-70
lines changed

LetsEatingTime_iOS.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
09237C7D2D11446D009C405E /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09237C7C2D11446D009C405E /* TokenManager.swift */; };
11+
09237C7F2D114481009C405E /* RequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09237C7E2D114481009C405E /* RequestInterceptor.swift */; };
12+
09237C812D1144EA009C405E /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09237C802D1144EA009C405E /* NetworkManager.swift */; };
1013
4C479A172CEB6E570070C2E9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4C901EA02CDA4A0C00115164 /* Assets.xcassets */; };
1114
4C5A6E4C2CECC4730066D868 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5A6E4B2CECC4730066D868 /* ChangePasswordView.swift */; };
1215
4C7977F92CDAFDAC00C12B69 /* ProductDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7977F82CDAFDAC00C12B69 /* ProductDetail.swift */; };
@@ -75,6 +78,9 @@
7578
/* End PBXCopyFilesBuildPhase section */
7679

7780
/* Begin PBXFileReference section */
81+
09237C7C2D11446D009C405E /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; };
82+
09237C7E2D114481009C405E /* RequestInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestInterceptor.swift; sourceTree = "<group>"; };
83+
09237C802D1144EA009C405E /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; };
7884
4C5A6E4B2CECC4730066D868 /* ChangePasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordView.swift; sourceTree = "<group>"; };
7985
4C7977F82CDAFDAC00C12B69 /* ProductDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetail.swift; sourceTree = "<group>"; };
8086
4C7977FA2CDAFDCE00C12B69 /* APIResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIResponse.swift; sourceTree = "<group>"; };
@@ -244,6 +250,7 @@
244250
children = (
245251
4CB2FD912CD8C6D100CF0F66 /* Product.swift */,
246252
4C7977F82CDAFDAC00C12B69 /* ProductDetail.swift */,
253+
09237C802D1144EA009C405E /* NetworkManager.swift */,
247254
);
248255
path = Product;
249256
sourceTree = "<group>";
@@ -294,6 +301,8 @@
294301
4C617B1A2CDC483600AD7263 /* Order */,
295302
4C617B182CDC482500AD7263 /* Product */,
296303
4C7977FA2CDAFDCE00C12B69 /* APIResponse.swift */,
304+
09237C7E2D114481009C405E /* RequestInterceptor.swift */,
305+
09237C7C2D11446D009C405E /* TokenManager.swift */,
297306
);
298307
path = Network;
299308
sourceTree = "<group>";
@@ -482,6 +491,8 @@
482491
4C7977F92CDAFDAC00C12B69 /* ProductDetail.swift in Sources */,
483492
4C7977FB2CDAFDCE00C12B69 /* APIResponse.swift in Sources */,
484493
4CB7169C2CD8A4980087DB2C /* MainView.swift in Sources */,
494+
09237C7D2D11446D009C405E /* TokenManager.swift in Sources */,
495+
09237C812D1144EA009C405E /* NetworkManager.swift in Sources */,
485496
4CB2FDA02CD9D58A00CF0F66 /* Meal.swift in Sources */,
486497
4CE8B5322CD7A8C300F920BC /* LetsEatingTime_iOSApp.swift in Sources */,
487498
4CB7169A2CD89DBD0087DB2C /* RegisterStdNmView.swift in Sources */,
@@ -494,6 +505,7 @@
494505
4CE8B5422CD7B67400F920BC /* LoginView.swift in Sources */,
495506
4CA7D7992CDB3C8900332631 /* OrderDetail.swift in Sources */,
496507
4CB2FDA22CD9D5AE00CF0F66 /* TodayMealView.swift in Sources */,
508+
09237C7F2D114481009C405E /* RequestInterceptor.swift in Sources */,
497509
4CA7D7952CDB3C7400332631 /* OrderRequest.swift in Sources */,
498510
4C5A6E4C2CECC4730066D868 /* ChangePasswordView.swift in Sources */,
499511
4C7D750A2CDA65F50011F2FA /* ProductDetailView.swift in Sources */,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>LetsEatingTimeWidgetExtension.xcscheme_^#shared#^_</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>0</integer>
11+
</dict>
12+
<key>LetsEatingTime_iOS.xcscheme_^#shared#^_</key>
13+
<dict>
14+
<key>orderHint</key>
15+
<integer>4</integer>
16+
</dict>
17+
</dict>
18+
</dict>
19+
</plist>

LetsEatingTime_iOS/LetsEatingTime_iOSApp.swift

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,57 @@ import SwiftUI
99

1010
@main
1111
struct LetsEatingTime_iOSApp: App {
12+
13+
@State private var isLoggedIn: Bool = false
14+
@State private var isTokenChecked: Bool = false
15+
1216
var body: some Scene {
1317
WindowGroup {
14-
OnboardingView()
18+
if isTokenChecked {
19+
if isLoggedIn {
20+
BottomNavigationView()
21+
} else {
22+
OnboardingView()
23+
}
24+
} else {
25+
ProgressView()
26+
.onAppear {
27+
checkAccessToken()
28+
}
29+
}
1530
}
1631
}
32+
33+
func checkAccessToken() {
34+
if let token = TokenManager.shared.accessToken {
35+
if isTokenExpired(token) {
36+
TokenManager.shared.refreshAccessToken { success, _ in
37+
DispatchQueue.main.async {
38+
isLoggedIn = success
39+
isTokenChecked = true
40+
}
41+
}
42+
} else {
43+
isLoggedIn = true
44+
isTokenChecked = true
45+
}
46+
} else {
47+
isTokenChecked = true
48+
}
49+
}
50+
51+
func isTokenExpired(_ token: String) -> Bool {
52+
let parts = token.split(separator: ".")
53+
guard parts.count == 3 else { return true }
54+
55+
let payloadData = Data(base64Encoded: String(parts[1])
56+
.replacingOccurrences(of: "-", with: "+")
57+
.replacingOccurrences(of: "_", with: "/") + "==") ?? Data()
58+
59+
guard let payload = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
60+
let exp = payload["exp"] as? TimeInterval else { return true }
61+
62+
let expirationDate = Date(timeIntervalSince1970: exp)
63+
return Date() > expirationDate
64+
}
1765
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// NetworkManager.swift
3+
// LetsEatingTime_iOS
4+
//
5+
// Created by 서승훈 on 12/17/24.
6+
//
7+
8+
import Alamofire
9+
10+
class NetworkManager {
11+
static let shared = NetworkManager()
12+
13+
private init() {
14+
let interceptor = RequestInterceptor()
15+
let session = Session(interceptor: interceptor)
16+
self.session = session
17+
}
18+
19+
var session: Session
20+
21+
func makeRequest() {
22+
let url = "http://example.com/api"
23+
session.request(url).responseJSON { response in
24+
switch response.result {
25+
case .success(let value):
26+
print("응답 성공: \(value)")
27+
case .failure(let error):
28+
print("응답 실패: \(error)")
29+
}
30+
}
31+
}
32+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// RequestInterceptor.swift
3+
// LetsEatingTime_iOS
4+
//
5+
// Created by 서승훈 on 12/17/24.
6+
//
7+
8+
import Foundation
9+
import Alamofire
10+
11+
class RequestInterceptor: Alamofire.RequestInterceptor {
12+
13+
func adapt(_ urlRequest: URLRequest, for session: Session) throws -> URLRequest {
14+
var urlRequest = urlRequest
15+
if let token = TokenManager.shared.accessToken {
16+
urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
17+
}
18+
return urlRequest
19+
}
20+
21+
func retry(_ request: Request, for session: Session, dueTo error: AFError, completion: @escaping (RetryResult) -> Void) {
22+
guard error.isResponseSerializationError else {
23+
completion(.doNotRetry)
24+
return
25+
}
26+
27+
if let statusCode = request.response?.statusCode, statusCode == 401 {
28+
TokenManager.shared.refreshAccessToken { success, errorMessage in
29+
if success {
30+
completion(.retry)
31+
} else {
32+
completion(.doNotRetry)
33+
print("토큰 갱신 실패: \(errorMessage ?? "알 수 없음")")
34+
}
35+
}
36+
} else {
37+
completion(.doNotRetry)
38+
}
39+
}
40+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//
2+
// TokenManager.swift
3+
// LetsEatingTime_iOS
4+
//
5+
// Created by 서승훈 on 12/17/24.
6+
//
7+
8+
import Foundation
9+
import Alamofire
10+
11+
class TokenManager {
12+
static let shared = TokenManager()
13+
14+
private let accessTokenKey = "accessToken"
15+
private let refreshTokenKey = "refreshToken"
16+
17+
var accessToken: String? {
18+
get { UserDefaults.standard.string(forKey: accessTokenKey) }
19+
set { UserDefaults.standard.set(newValue, forKey: accessTokenKey) }
20+
}
21+
22+
var refreshToken: String? {
23+
get { UserDefaults.standard.string(forKey: refreshTokenKey) }
24+
set { UserDefaults.standard.set(newValue, forKey: refreshTokenKey) }
25+
}
26+
27+
func refreshAccessToken(completion: @escaping (Bool, String?) -> Void) {
28+
guard let refreshToken = self.refreshToken else {
29+
completion(false, "Refresh token is missing")
30+
return
31+
}
32+
33+
let url = "http://www.dgsw-team-alt.xyz/api/account/refresh.do"
34+
let headers: HTTPHeaders = [
35+
"Authorization": "Bearer \(refreshToken)"
36+
]
37+
38+
AF.request(url, method: .post, headers: headers)
39+
.validate()
40+
.responseJSON { response in
41+
switch response.result {
42+
case .success(let value):
43+
if let json = value as? [String: Any],
44+
let data = json["data"] as? [String: Any],
45+
let newAccessToken = data["accessToken"] as? String,
46+
let newRefreshToken = data["refreshToken"] as? String {
47+
self.accessToken = newAccessToken
48+
self.refreshToken = newRefreshToken
49+
completion(true, nil)
50+
} else {
51+
completion(false, "Invalid response format")
52+
}
53+
case .failure(let error):
54+
completion(false, error.localizedDescription)
55+
}
56+
}
57+
}
58+
}

LetsEatingTime_iOS/View/Navigation/BottomNavigationView.swift

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -58,27 +58,18 @@ struct BottomNavigationView: View {
5858
Image(systemName: "cart.fill")
5959
}
6060
.tag(0)
61-
.onAppear {
62-
checkAccessToken()
63-
}
6461

6562
MainView()
6663
.tabItem {
6764
Image(systemName: "house.fill")
6865
}
6966
.tag(1)
70-
.onAppear {
71-
checkAccessToken()
72-
}
7367

7468
ProfileView()
7569
.tabItem {
7670
Image(systemName: "person.crop.circle.fill")
7771
}
7872
.tag(2)
79-
.onAppear {
80-
checkAccessToken()
81-
}
8273
}
8374
.navigationBarBackButtonHidden()
8475
.accentColor(Color(hex: "#FE6B4B"))
@@ -94,66 +85,6 @@ struct BottomNavigationView: View {
9485
}
9586
}
9687

97-
private func checkAccessToken() {
98-
guard let accessToken = UserDefaults.standard.string(forKey: "accessToken") else {
99-
isAccessTokenValid = false
100-
return
101-
}
102-
103-
if isTokenExpired(accessToken) {
104-
refreshTokens { success, error in
105-
if !success {
106-
isAccessTokenValid = false
107-
}
108-
}
109-
}
110-
}
111-
112-
private func isTokenExpired(_ token: String) -> Bool {
113-
let parts = token.split(separator: ".")
114-
guard parts.count == 3 else { return true }
115-
116-
let payloadData = Data(base64Encoded: String(parts[1]).replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") + "==") ?? Data()
117-
guard let payload = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
118-
let exp = payload["exp"] as? TimeInterval else { return true }
119-
120-
let expirationDate = Date(timeIntervalSince1970: exp)
121-
return Date() > expirationDate
122-
}
123-
124-
private func refreshTokens(completion: @escaping (Bool, String?) -> Void) {
125-
guard let refreshToken = UserDefaults.standard.string(forKey: "refreshToken") else {
126-
completion(false, "Refresh token is missing")
127-
return
128-
}
129-
130-
let url = "http://www.dgsw-team-alt.xyz/api/account/refresh.do"
131-
let headers: HTTPHeaders = [
132-
"Authorization": "Bearer \(refreshToken)"
133-
]
134-
135-
AF.request(url, method: .post, headers: headers)
136-
.validate()
137-
.responseJSON { response in
138-
switch response.result {
139-
case .success(let value):
140-
if let json = value as? [String: Any],
141-
let data = json["data"] as? [String: Any],
142-
let newAccessToken = data["accessToken"] as? String,
143-
let newRefreshToken = data["refreshToken"] as? String {
144-
145-
UserDefaults.standard.set(newAccessToken, forKey: "accessToken")
146-
UserDefaults.standard.set(newRefreshToken, forKey: "refreshToken")
147-
completion(true, nil)
148-
} else {
149-
completion(false, "Invalid response format")
150-
}
151-
case .failure(let error):
152-
completion(false, error.localizedDescription)
153-
}
154-
}
155-
}
156-
15788
private func logout() {
15889
UserDefaults.standard.removeObject(forKey: "accessToken")
15990
UserDefaults.standard.removeObject(forKey: "refreshToken")

0 commit comments

Comments
 (0)