From 4b5fa133f53d1d674bdff2462669db0c1f29a45a Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Tue, 13 Oct 2026 13:57:44 +0900 Subject: [PATCH] =?UTF-8?q?fix(deeplink):=20=EC=BB=A4=EB=AE=A4=EB=8B=88?= =?UTF-8?q?=ED=8B=B0=20=EB=A7=81=ED=81=AC=EB=A5=BC=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=83=81=EC=84=B8=EC=97=90=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/App/AppDeepLinkHandler.swift | 41 +++++------ work/tests/AppDeepLinkGiftDetailCheck.swift | 70 ++++++++++++++++++- 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/SodaLive/Sources/App/AppDeepLinkHandler.swift b/SodaLive/Sources/App/AppDeepLinkHandler.swift index 50b6386c..00bd2927 100644 --- a/SodaLive/Sources/App/AppDeepLinkHandler.swift +++ b/SodaLive/Sources/App/AppDeepLinkHandler.swift @@ -6,7 +6,7 @@ enum AppDeepLinkAction { case content(contentId: Int) case series(seriesId: Int) case giftDetail(applicationNo: String) - case community(creatorId: Int, postId: Int?) + case community(postId: Int) case message case audition } @@ -74,16 +74,9 @@ enum AppDeepLinkHandler { case .giftDetail(let applicationNo): AppState.shared.setAppStep(step: .giftDetail(applicationNo: applicationNo)) - case .community(let creatorId, let postId): - guard creatorId > 0 else { return } - - if let postId = postId, postId > 0 { - AppState.shared.setPendingCommunityCommentDeepLink(creatorId: creatorId, postId: postId) - } else { - AppState.shared.clearPendingCommunityCommentDeepLink() - } - - AppState.shared.setAppStep(step: .creatorCommunityAll(creatorId: creatorId)) + case .community(let postId): + guard postId > 0 else { return } + AppState.shared.setAppStep(step: .creatorChannelCommunityPostDetail(postId: postId)) case .message: AppState.shared.setAppStep(step: .message) @@ -104,6 +97,12 @@ enum AppDeepLinkHandler { private static func parseAction(url: URL) -> AppDeepLinkAction? { let components = URLComponents(url: url, resolvingAgainstBaseURL: false) + let host = components?.host?.lowercased() ?? "" + let pathRoute = url.pathComponents.first { $0 != "/" && !$0.isEmpty }?.lowercased() + + if host == "community" || (host.isEmpty && pathRoute == "community") { + return parsePathStyle(url: url, components: components) + } if let routeAction = parsePathStyle(url: url, components: components) { return routeAction @@ -160,7 +159,10 @@ enum AppDeepLinkHandler { } if let communityId = queryMap["community_id"], let value = Int(communityId), value > 0 { - return .community(creatorId: value, postId: communityPostId(queryMap: queryMap)) + guard let postId = communityPostId(queryMap: queryMap) else { + return nil + } + return .community(postId: postId) } if queryMap["message_id"] != nil { @@ -208,17 +210,10 @@ enum AppDeepLinkHandler { return .giftDetail(applicationNo: applicationNo) case "community": - let postId = communityPostId(queryItems: components?.queryItems) - - if let identifier = identifier, let creatorId = Int(identifier), creatorId > 0 { - return .community(creatorId: creatorId, postId: postId) - } - - guard let creatorId = fallbackCommunityCreatorId() else { + guard let postId = communityPostId(queryItems: components?.queryItems) else { return nil } - - return .community(creatorId: creatorId, postId: postId) + return .community(postId: postId) case "message": return .message @@ -256,8 +251,4 @@ enum AppDeepLinkHandler { return nil } - private static func fallbackCommunityCreatorId() -> Int? { - let userId = UserDefaults.int(forKey: .userId) - return userId > 0 ? userId : nil - } } diff --git a/work/tests/AppDeepLinkGiftDetailCheck.swift b/work/tests/AppDeepLinkGiftDetailCheck.swift index 86d00d2a..5705fdcb 100644 --- a/work/tests/AppDeepLinkGiftDetailCheck.swift +++ b/work/tests/AppDeepLinkGiftDetailCheck.swift @@ -7,6 +7,7 @@ enum AppStep: Equatable { case contentDetail(contentId: Int) case seriesDetail(seriesId: Int) case creatorCommunityAll(creatorId: Int) + case creatorChannelCommunityPostDetail(postId: Int) case message case audition case giftDetail(applicationNo: String) @@ -14,7 +15,8 @@ enum AppStep: Equatable { enum UserDefaultsKey { case userId } enum UserDefaults { - static func int(forKey: UserDefaultsKey) -> Int { 0 } + static var userId = 0 + static func int(forKey: UserDefaultsKey) -> Int { userId } } final class AppState { @@ -24,9 +26,10 @@ final class AppState { var appliedStep: AppStep? var isPushRoomFromDeepLink = false var pushRoomId = 0 + var pendingCommentPostId: Int? func setAppStep(step: AppStep) { appliedStep = step } - func setPendingCommunityCommentDeepLink(creatorId: Int, postId: Int) {} - func clearPendingCommunityCommentDeepLink() {} + func setPendingCommunityCommentDeepLink(creatorId: Int, postId: Int) { pendingCommentPostId = postId } + func clearPendingCommunityCommentDeepLink() { pendingCommentPostId = nil } } let APPSCHEME = "soda" @@ -104,6 +107,67 @@ enum GiftDetailDeepLinkCheck { check(AppDeepLinkHandler.handle(urlString: "voiceon://?content_id=42&deepLinkValue=GIFT_DETAIL&deepLinkId=GIFT-2026-007"), "existing content route accepted") settle() check(AppState.shared.appliedStep == .contentDetail(contentId: 42), "existing content route still applies") + checkCommunityLinks() print("PASS AppDeepLinkGiftDetailCheck assertions=\(assertions)") } + + static func checkCommunityLinks() { + for userId in [0, 42] { + UserDefaults.userId = userId + for source in [AppDeepLinkSource.external, .notificationList] { + for url in [ + "voiceon://community?postId=12345", + "voiceon-test://community?post_id=12345", + "voiceon:///community?POSTID=12345", + "voiceon://community/77?postId=12345", + "voiceon://?community_id=77&postId=12345" + ] { + AppState.shared.appliedStep = nil + AppState.shared.pendingCommentPostId = nil + check(AppDeepLinkHandler.handle(urlString: url, source: source), "community link accepted: \(url), userId=\(userId)") + settle() + check(AppState.shared.appliedStep == .creatorChannelCommunityPostDetail(postId: 12345), "community link opens requested post detail: \(url)") + check(AppState.shared.pendingCommentPostId == nil, "detail link does not queue legacy comment sheet") + } + } + } + + for rootStep in [AppStep.main, .splash] { + AppState.shared.rootStep = rootStep + for route in ["voiceon://community", "voiceon:///community", "voiceon://community/77", "voiceon://?community_id=77"] { + for parameter in ["", "postId=", "postId=abc", "postId=0", "postId=-1", "postId=1.5", "postId=999999999999999999999999", "post_id=0"] { + let separator = route.contains("?") ? "&" : "?" + let url = route + separator + parameter + AppState.shared.appliedStep = .contentDetail(contentId: 42) + AppState.shared.pendingDeepLinkAction = .content(contentId: 99) + check(!AppDeepLinkHandler.handle(urlString: url), "invalid community link rejected: \(url)") + settle() + check(AppState.shared.appliedStep == .contentDetail(contentId: 42), "invalid postId leaves current route unchanged") + if case .content(let contentId)? = AppState.shared.pendingDeepLinkAction { + check(contentId == 99, "invalid postId preserves existing deferred action") + } else { + check(false, "invalid postId preserves existing deferred action") + } + } + } + } + + check(!AppDeepLinkHandler.handle(urlString: "voiceon://community?postId=bad&content_id=42"), "invalid community route does not fall through to content") + check(!AppDeepLinkHandler.handle(urlString: "voiceon:///community?message_id=42"), "missing postId does not fall through to message") + + AppState.shared.rootStep = .splash + AppState.shared.appliedStep = nil + AppState.shared.pendingDeepLinkAction = nil + check(AppDeepLinkHandler.handle(urlString: "voiceon://community?postId=12345"), "splash accepts community detail") + settle() + check(AppState.shared.appliedStep == nil, "splash defers detail navigation") + guard let action = AppState.shared.pendingDeepLinkAction else { + check(false, "splash stores community detail action") + return + } + AppState.shared.rootStep = .main + AppState.shared.pendingDeepLinkAction = nil + AppDeepLinkHandler.apply(action: action) + check(AppState.shared.appliedStep == .creatorChannelCommunityPostDetail(postId: 12345), "deferred community link opens requested post detail") + } }