Skip to content
Merged
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
12 changes: 6 additions & 6 deletions Atcha-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2516,7 +2516,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482;
FRAMEWORK_SEARCH_PATHS = (
Expand All @@ -2539,7 +2539,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.9.2;
MARKETING_VERSION = 1.9.5;
PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -2563,7 +2563,7 @@
CODE_SIGN_ENTITLEMENTS = "Atcha-iOS/Atcha-iOS.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = 23SCTLK482;
EXCLUDED_ARCHS = "";
FRAMEWORK_SEARCH_PATHS = (
Expand All @@ -2586,7 +2586,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.9.2;
MARKETING_VERSION = 1.9.5;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2611,7 +2611,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482;
FRAMEWORK_SEARCH_PATHS = (
Expand All @@ -2634,7 +2634,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.9.2;
MARKETING_VERSION = 1.9.5;
PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Stage"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
2 changes: 2 additions & 0 deletions Atcha-iOS/Core/Manager/Discord/DiscordWebhookManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class DiscordWebhookManager {
private let webhookURLString = "https://discord.com/api/webhooks/1483870710018474066/qyzNBI1Bwr7J5tQDrPx2-mOcej_9yLSOk5Bmlmza2D-4nSWqvWgcMd4CZDziG4vkpKrm"

func sendErrorLog(
baseURL: String,
statusCode: Int,
method: String,
path: String,
Expand Down Expand Up @@ -55,6 +56,7 @@ final class DiscordWebhookManager {
"title": "서버 에러 상세 보고",
"color": 16711680,
"fields": [
["name": "Base URL", "value": "`\(baseURL)`", "inline": false],
["name": "Method & Path", "value": "`\(method) \(path)`", "inline": false],
["name": "HTTP Status", "value": "\(statusCode)", "inline": true],
["name": "responseCode", "value": responseCode, "inline": true],
Expand Down
4 changes: 2 additions & 2 deletions Atcha-iOS/Core/Manager/Proximity/ProximityManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ final class ProximityManager {

// 판별 임계값(미터). 기본 1km.
private let threshold: CLLocationDistance
static let shared = ProximityManager(thresholdMeters: 1_000)
static let shared = ProximityManager(thresholdMeters: 800)

init(thresholdMeters: CLLocationDistance = 1_000) {
init(thresholdMeters: CLLocationDistance = 800) {
self.threshold = thresholdMeters
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ final class ProximityViewController: BaseViewController<ProximityViewModel> {
containerView.snp.makeConstraints {
$0.leading.trailing.equalToSuperview()
$0.bottom.equalToSuperview()
$0.height.equalTo(195)
$0.height.equalTo(182)
}

labelStackView.snp.makeConstraints {
Expand Down
3 changes: 2 additions & 1 deletion Atcha-iOS/Core/Network/API/APIServiceImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ extension APIServiceImpl {
}

DiscordWebhookManager.shared.sendErrorLog(
baseURL: NetworkConstant.baseURL,
statusCode: statusCode,
method: method,
path: serverPath,
Expand All @@ -136,7 +137,7 @@ extension APIServiceImpl {

let apiError = APIError.serverError(statusCode: statusCode, responseCode: responseCode)

NotificationCenter.default.post(name: .apiErrorOccurred, object: apiError)
// NotificationCenter.default.post(name: .apiErrorOccurred, object: apiError)
continuation.resume(throwing: apiError)
}
}
28 changes: 23 additions & 5 deletions Atcha-iOS/Core/Network/Token/TokenInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ final class TokenInterceptor: RequestInterceptor, @unchecked Sendable {
]

if publicPaths.contains(where: { path.hasSuffix($0) }) {
completion(.success(request))
return
}
completion(.success(request))
return
}

if path.contains("/auth/logout") {
if let refreshToken = tokenStorage.refreshToken {
Expand All @@ -65,7 +65,6 @@ final class TokenInterceptor: RequestInterceptor, @unchecked Sendable {
return
}


let path = request.request?.url?.path ?? "unknown"

if path.contains("/auth/reissue") {
Expand All @@ -78,6 +77,8 @@ final class TokenInterceptor: RequestInterceptor, @unchecked Sendable {
return
}

let actualHeaders = request.request?.allHTTPHeaderFields ?? [:]

guard let refreshToken = tokenStorage.refreshToken else {
SessionController.shared.expireAndRouteToLogin()
completion(.doNotRetry)
Expand Down Expand Up @@ -109,12 +110,29 @@ final class TokenInterceptor: RequestInterceptor, @unchecked Sendable {
return
}

let successBody = [
"newAccessToken": p.accessToken,
"newRefreshToken": p.refreshToken
]

DiscordWebhookManager.shared.sendErrorLog(
baseURL: NetworkConstant.baseURL,
statusCode: 200,
method: "GET",
path: "/auth/reissue",
responseCode: "REISSUE_SUCCESS",
message: "토큰 재발급에 성공하여 새로운 토큰을 수신했습니다.",
requestHeaders: actualHeaders,
requestBody: successBody, // 여기서 받은 토큰 정보를 보냅니다.
requestParameters: nil
)

self.tokenStorage.accessToken = p.accessToken
self.tokenStorage.refreshToken = p.refreshToken

waiters.forEach { $0(.retry) }

case .failure(let error):
case .failure(_):
SessionController.shared.expireAndRouteToLogin()
waiters.forEach { $0(.doNotRetry) }
}
Expand Down
48 changes: 28 additions & 20 deletions Atcha-iOS/Data/Repository/CourseRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,8 @@ final class CourseRepositoryImpl: CourseRepository {
throw URLError(.badServerResponse)
}

// 401 외의 코드도 명확히 분기
if http.statusCode == 401 {
if let tokens = await refreshToken() {
tokenStorage.accessToken = tokens.accessToken
if let rt = tokens.refreshToken { tokenStorage.refreshToken = rt }
print("재발급 성공 → 스트림 재연결")
await startStream(request, continuation: continuation)
return
} else {
continuation.finish(throwing: NSError(domain: "CourseRepository", code: 401, userInfo: [NSLocalizedDescriptionKey: "토큰 재발급 실패"]))
return
}
} else if http.statusCode != 200 {
// 서버가 JSON 에러 바디를 줄 수 있으니 한번 읽어봄
// bytes(for:)는 스트림이므로 바디를 통째로 읽기 어렵다 → 상태만으로 에러 처리
// HTTP 401 체크는 제거 (서버가 200으로 주기 때문)
if http.statusCode != 200 {
continuation.finish(throwing: NSError(domain: "CourseRepository", code: http.statusCode, userInfo: [NSLocalizedDescriptionKey: "SSE 연결 실패 (\(http.statusCode))"]))
return
}
Expand All @@ -100,23 +87,41 @@ final class CourseRepositoryImpl: CourseRepository {
let events = parser.feed(Data([byte]))
for event in events {
guard !event.data.isEmpty, let payload = event.data.data(using: .utf8) else { continue }

// 1. 데이터 내부의 에러 코드 확인 (TOK_001)
if let errorCheck = try? JSONDecoder().decode(SSEErrorPayload.self, from: payload),
errorCheck.responseCode == "TOK_001" {

if let tokens = await refreshToken() {
tokenStorage.accessToken = tokens.accessToken
if let rt = tokens.refreshToken { tokenStorage.refreshToken = rt }
print("재발급 성공 → 스트림 재연결")
// 현재 스트림을 종료하지 않고 새 연결 시도
await startStream(request, continuation: continuation)
return
} else {
continuation.finish(throwing: NSError(domain: "CourseRepository", code: 401, userInfo: [NSLocalizedDescriptionKey: "토큰 재발급 실패"]))
return
}
}

// 2. 정상 데이터 디코딩
do {
let decoded = try JSONDecoder().decode(CourseSearchResponse.self, from: payload)
print("데이터 수신: \(decoded)")
continuation.yield(decoded)
} catch {
// 서버가 keep-alive ping 이나 텍스트를 보낼 수 있으므로 디코드 실패는 경고만
print("SSE decode 실패:", error, "raw:", event.data)
// 단순 텍스트나 핑 데이터인 경우 무시
print("SSE decode 실패 (데이터 무시):", error, "raw:", event.data)
}
}
}
} catch {
// 실제 네트워크 오류(연결 끊김 등)
print("SSE stream read error:", error)
continuation.finish(throwing: error)
return
}

// 서버가 조용히 닫은 케이스
if !receivedAny {
continuation.finish(throwing: NSError(domain: "CourseRepository", code: -1, userInfo: [NSLocalizedDescriptionKey: "SSE에서 응답이 없습니다."]))
return
Expand Down Expand Up @@ -182,6 +187,9 @@ final class CourseRepositoryImpl: CourseRepository {
return nil
}
}

}

struct SSEErrorPayload: Decodable {
let responseCode: String?
let message: String?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "bus-chevron-right.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "bus-chevron-right@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "bus-chevron-right@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "station-checron_down.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "station-checron_down@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "station-checron_down@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Atcha-iOS/DesignSource/AtchaLottie/RefreshView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ final class RefreshView: UIView {
}
animationView.snp.makeConstraints { make in
make.center.equalTo(refreshImageView)
make.size.equalTo(24)
make.size.equalTo(36)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class BusDetailViewController: BaseViewController<BusDetailViewModel> {
}

refreshButton.snp.makeConstraints { make in
make.size.equalTo(48)
make.size.equalTo(52)
make.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).inset(16)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class BusInfoViewController: BaseViewController<BusInfoViewModel> {
}

refreshButton.snp.makeConstraints { make in
make.size.equalTo(48)
make.size.equalTo(52)
make.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).inset(16)
}
Expand Down
Loading
Loading