LINE 로그인 지원 추가

LINE 로그인 요청과 토큰 처리 흐름을 추가함
This commit is contained in:
Yu Sung
2026-01-28 19:05:42 +09:00
parent 5e85b1d679
commit 42e375ec4b
10 changed files with 131 additions and 3 deletions

View File

@@ -150,6 +150,7 @@ struct LoginView: View {
Image("ic_login_line")
.onTapGesture {
hideKeyboard()
viewModel.loginWithLine()
}
Image("ic_login_x")

View File

@@ -15,12 +15,14 @@ import UIKit
import GoogleSignIn
import KakaoSDKUser
import KakaoSDKAuth
import LineSDK
final class LoginViewModel: NSObject, ObservableObject {
private let appViewModel = AppViewModel()
private let repository = UserRepository()
private var subscription = Set<AnyCancellable>()
private var currentNonce: String?
private var currentLineNonce: String?
@Published var email = ""
@Published var password = ""
@@ -127,6 +129,49 @@ final class LoginViewModel: NSObject, ObservableObject {
KakaoSDKUser.UserApi.shared.loginWithKakaoAccount(completion: loginHandler)
}
}
func loginWithLine() {
guard let presentingViewController = presentingViewController() else {
self.errorMessage = I18n.Login.Line.openFailed
self.isShowPopup = true
return
}
let nonce = randomNonceString()
currentLineNonce = nonce
var parameters = LoginManager.Parameters()
parameters.IDTokenNonce = nonce
DispatchQueue.main.async {
LoginManager.shared.login(permissions: [.profile, .openID], in: presentingViewController, parameters: parameters) { result in
switch result {
case .success(let loginResult):
guard let identityToken = loginResult.accessToken.IDTokenRaw, !identityToken.isEmpty else {
self.currentLineNonce = nil
self.errorMessage = I18n.Login.Line.tokenMissing
self.isShowPopup = true
return
}
guard let lineNonce = self.currentLineNonce else {
self.errorMessage = I18n.Common.commonError
self.isShowPopup = true
return
}
self.currentLineNonce = nil
self.loginWithLine(identityToken: identityToken, nonce: lineNonce)
case .failure(let error):
self.currentLineNonce = nil
ERROR_LOG(error.localizedDescription)
self.errorMessage = I18n.Login.Line.signInFailed
self.isShowPopup = true
}
}
}
}
private func loginWithApple(identityToken: String, nonce: String) {
let pushToken = UserDefaults.string(forKey: .pushToken)
@@ -209,6 +254,33 @@ final class LoginViewModel: NSObject, ObservableObject {
.store(in: &subscription)
}
private func loginWithLine(identityToken: String, nonce: String) {
let pushToken = UserDefaults.string(forKey: .pushToken)
let marketingPid = UserDefaults.string(forKey: .marketingPid)
let request = SocialLoginRequest(
container: "ios",
pushToken: pushToken.isEmpty ? nil : pushToken,
marketingPid: marketingPid.isEmpty ? nil : marketingPid,
identityToken: identityToken,
nonce: nonce
)
isLoading = true
repository.loginLine(request: request)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
self.isLoading = false
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { response in
self.handleLoginResponse(response)
}
.store(in: &subscription)
}
private func presentingViewController() -> UIViewController? {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }

View File

@@ -13,6 +13,7 @@ enum UserApi {
case loginApple(request: SocialLoginRequest)
case loginGoogle(request: SocialLoginRequest, idToken: String)
case loginKakao(request: SocialLoginRequest, accessToken: String)
case loginLine(request: SocialLoginRequest)
case signUp(request: SignUpRequest)
case findPassword(request: ForgotPasswordRequest)
case searchUser(nickname: String)
@@ -58,6 +59,9 @@ extension UserApi: TargetType {
case .loginKakao:
return "/member/login/kakao"
case .loginLine:
return "/member/login/line"
case .signUp:
return "/member/signup/v2"
@@ -132,7 +136,7 @@ extension UserApi: TargetType {
var method: Moya.Method {
switch self {
case .login, .loginApple, .loginGoogle, .loginKakao, .signUp, .findPassword, .notification, .logout, .logoutAllDevice, .signOut, .creatorFollow, .creatorUnFollow, .memberBlock, .memberUnBlock,
case .login, .loginApple, .loginGoogle, .loginKakao, .loginLine, .signUp, .findPassword, .notification, .logout, .logoutAllDevice, .signOut, .creatorFollow, .creatorUnFollow, .memberBlock, .memberUnBlock,
.profileImageUpdate:
return .post
@@ -157,6 +161,9 @@ extension UserApi: TargetType {
case .loginKakao(let request, _):
return .requestJSONEncodable(request)
case .loginLine(let request):
return .requestJSONEncodable(request)
case .signUp(let request):
return .requestJSONEncodable(request)
@@ -220,7 +227,7 @@ extension UserApi: TargetType {
var headers: [String : String]? {
switch self {
case .login, .loginApple, .signUp, .findPassword:
case .login, .loginApple, .loginLine, .signUp, .findPassword:
return nil
case .loginGoogle(_, let idToken):

View File

@@ -28,6 +28,10 @@ final class UserRepository {
func loginKakao(request: SocialLoginRequest, accessToken: String) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.loginKakao(request: request, accessToken: accessToken))
}
func loginLine(request: SocialLoginRequest) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.loginLine(request: request))
}
func signUp(request: SignUpRequest) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.signUp(request: request))