@package-kr/react-native-kakao-signin 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/packagekr/kakao/RNKakaoLoginModule.kt +347 -0
- package/android/src/main/java/com/packagekr/kakao/RNKakaoLoginPackage.kt +46 -0
- package/ios/RNKakaoError.swift +100 -0
- package/ios/RNKakaoLogin-Bridging-Header.h +1 -0
- package/ios/RNKakaoLogin.h +8 -0
- package/ios/RNKakaoLogin.mm +13 -0
- package/ios/RNKakaoLogin.swift +256 -0
- package/ios/RNKakaoLoginHelper.swift +25 -0
- package/ios/RNKakaoLoginLoader.m +148 -0
- package/kakao-login.podspec +36 -0
- package/package.json +54 -0
- package/src/NativeKakaoLogin.ts +15 -0
- package/src/index.ts +45 -0
- package/src/types/index.ts +90 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
package com.packagekr.kakao
|
|
2
|
+
|
|
3
|
+
import android.content.pm.PackageManager
|
|
4
|
+
import com.facebook.react.bridge.Arguments
|
|
5
|
+
import com.facebook.react.bridge.Promise
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.bridge.ReactMethod
|
|
8
|
+
import com.facebook.react.bridge.WritableMap
|
|
9
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
10
|
+
import com.kakao.sdk.auth.TokenManagerProvider
|
|
11
|
+
import com.kakao.sdk.common.KakaoSdk
|
|
12
|
+
import com.kakao.sdk.common.model.ClientError
|
|
13
|
+
import com.kakao.sdk.common.model.ClientErrorCause
|
|
14
|
+
import com.kakao.sdk.common.util.Utility
|
|
15
|
+
import com.kakao.sdk.user.UserApiClient
|
|
16
|
+
import java.text.SimpleDateFormat
|
|
17
|
+
import java.util.Date
|
|
18
|
+
import java.util.Locale
|
|
19
|
+
import java.util.TimeZone
|
|
20
|
+
|
|
21
|
+
@ReactModule(name = RNKakaoSigninModule.NAME)
|
|
22
|
+
class RNKakaoSigninModule(
|
|
23
|
+
reactContext: ReactApplicationContext
|
|
24
|
+
) : NativeKakaoLoginSpec(reactContext) {
|
|
25
|
+
|
|
26
|
+
companion object {
|
|
27
|
+
const val NAME = "RNKakaoSignin"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
init {
|
|
31
|
+
configureKakaoSdk()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun getName(): String {
|
|
35
|
+
return NAME
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// SDK 초기화
|
|
39
|
+
private fun configureKakaoSdk() {
|
|
40
|
+
if (KakaoSdk.isInitialized) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
val appKey = resolveMetaData("com.kakao.sdk.AppKey")
|
|
45
|
+
?: resolveString("kakao_app_key")
|
|
46
|
+
?: return
|
|
47
|
+
|
|
48
|
+
val customScheme = resolveString("kakao_custom_scheme")
|
|
49
|
+
|
|
50
|
+
if (customScheme.isNullOrBlank()) {
|
|
51
|
+
KakaoSdk.init(reactApplicationContext, appKey)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
KakaoSdk.init(reactApplicationContext, appKey, customScheme)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 카카오톡 로그인
|
|
59
|
+
@ReactMethod
|
|
60
|
+
override fun login(promise: Promise) {
|
|
61
|
+
val activity = currentActivity
|
|
62
|
+
|
|
63
|
+
if (activity == null) {
|
|
64
|
+
promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist")
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!UserApiClient.instance.isKakaoTalkLoginAvailable(activity)) {
|
|
69
|
+
loginWithAccount(promise)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
UserApiClient.instance.loginWithKakaoTalk(activity) { token, error ->
|
|
74
|
+
when {
|
|
75
|
+
token != null -> promise.resolve(resolveToken(token.accessToken, token.refreshToken, token.idToken, token.scopes))
|
|
76
|
+
error is ClientError && error.reason == ClientErrorCause.Cancelled ->
|
|
77
|
+
promise.reject("E_CANCELLED_OPERATION", error.message, error)
|
|
78
|
+
error != null -> loginWithAccount(promise)
|
|
79
|
+
else -> promise.reject("E_UNKNOWN_ERROR", "Login failed")
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 카카오계정 로그인
|
|
85
|
+
@ReactMethod
|
|
86
|
+
override fun loginWithKakaoAccount(promise: Promise) {
|
|
87
|
+
loginWithAccount(promise)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 로그아웃
|
|
91
|
+
@ReactMethod
|
|
92
|
+
override fun logout(promise: Promise) {
|
|
93
|
+
UserApiClient.instance.logout { error ->
|
|
94
|
+
if (error != null) {
|
|
95
|
+
promise.reject("E_FAILED_OPERATION", error.message, error)
|
|
96
|
+
return@logout
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
promise.resolve(true)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 연결 해제
|
|
104
|
+
@ReactMethod
|
|
105
|
+
override fun unlink(promise: Promise) {
|
|
106
|
+
UserApiClient.instance.unlink { error ->
|
|
107
|
+
if (error != null) {
|
|
108
|
+
promise.reject("E_FAILED_OPERATION", error.message, error)
|
|
109
|
+
return@unlink
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
promise.resolve(true)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 액세스 토큰 조회
|
|
117
|
+
@ReactMethod
|
|
118
|
+
override fun getAccessToken(promise: Promise) {
|
|
119
|
+
val token = TokenManagerProvider.instance.manager.getToken()
|
|
120
|
+
|
|
121
|
+
if (token == null) {
|
|
122
|
+
promise.resolve(null)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
promise.resolve(resolveToken(token.accessToken, token.refreshToken, token.idToken, token.scopes))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 프로필 조회
|
|
130
|
+
@ReactMethod
|
|
131
|
+
override fun getProfile(promise: Promise) {
|
|
132
|
+
UserApiClient.instance.me { user, error ->
|
|
133
|
+
if (error != null) {
|
|
134
|
+
promise.reject("E_FAILED_OPERATION", error.message, error)
|
|
135
|
+
return@me
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (user == null) {
|
|
139
|
+
promise.reject("E_PROFILE_NOT_FOUND", "Profile not found")
|
|
140
|
+
return@me
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
val profile = Arguments.createMap()
|
|
144
|
+
val account = user.kakaoAccount
|
|
145
|
+
val detail = account?.profile
|
|
146
|
+
|
|
147
|
+
profile.putString("id", user.id?.toString())
|
|
148
|
+
profile.putString("name", account?.name)
|
|
149
|
+
profile.putString("email", account?.email)
|
|
150
|
+
profile.putString("nickname", detail?.nickname)
|
|
151
|
+
profile.putString("profileImageUrl", detail?.profileImageUrl)
|
|
152
|
+
profile.putString("thumbnailImageUrl", detail?.thumbnailImageUrl)
|
|
153
|
+
profile.putString("phoneNumber", account?.phoneNumber)
|
|
154
|
+
profile.putString("ageRange", account?.ageRange?.toString())
|
|
155
|
+
profile.putString("birthday", account?.birthday)
|
|
156
|
+
profile.putString("birthdayType", account?.birthdayType?.toString())
|
|
157
|
+
profile.putString("birthyear", account?.birthyear)
|
|
158
|
+
profile.putString("gender", account?.gender?.toString())
|
|
159
|
+
account?.isEmailValid?.let { profile.putBoolean("isEmailValid", it) }
|
|
160
|
+
account?.isEmailVerified?.let { profile.putBoolean("isEmailVerified", it) }
|
|
161
|
+
account?.isKorean?.let { profile.putBoolean("isKorean", it) }
|
|
162
|
+
account?.hasEmail?.let { profile.putBoolean("hasEmail", it) }
|
|
163
|
+
account?.hasPhoneNumber?.let { profile.putBoolean("hasPhoneNumber", it) }
|
|
164
|
+
account?.hasBirthday?.let { profile.putBoolean("hasBirthday", it) }
|
|
165
|
+
account?.hasBirthyear?.let { profile.putBoolean("hasBirthyear", it) }
|
|
166
|
+
account?.hasAgeRange?.let { profile.putBoolean("hasAgeRange", it) }
|
|
167
|
+
account?.hasGender?.let { profile.putBoolean("hasGender", it) }
|
|
168
|
+
detail?.isDefaultImage?.let { profile.putBoolean("isDefaultImage", it) }
|
|
169
|
+
detail?.isDefaultNickname?.let { profile.putBoolean("isDefaultNickname", it) }
|
|
170
|
+
profile.putString("connectedAt", formatDate(user.connectedAt))
|
|
171
|
+
profile.putString("synchedAt", formatDate(user.synchedAt))
|
|
172
|
+
account?.isLeapMonth?.let { profile.putBoolean("isLeapMonth", it) }
|
|
173
|
+
account?.ci?.let { profile.putString("ci", it) }
|
|
174
|
+
profile.putString("ciAuthenticatedAt", formatDate(account?.ciAuthenticatedAt))
|
|
175
|
+
account?.ageRangeNeedsAgreement?.let { profile.putBoolean("ageRangeNeedsAgreement", it) }
|
|
176
|
+
account?.birthdayNeedsAgreement?.let { profile.putBoolean("birthdayNeedsAgreement", it) }
|
|
177
|
+
account?.birthyearNeedsAgreement?.let { profile.putBoolean("birthyearNeedsAgreement", it) }
|
|
178
|
+
account?.emailNeedsAgreement?.let { profile.putBoolean("emailNeedsAgreement", it) }
|
|
179
|
+
account?.genderNeedsAgreement?.let { profile.putBoolean("genderNeedsAgreement", it) }
|
|
180
|
+
account?.isKoreanNeedsAgreement?.let { profile.putBoolean("isKoreanNeedsAgreement", it) }
|
|
181
|
+
account?.phoneNumberNeedsAgreement?.let { profile.putBoolean("phoneNumberNeedsAgreement", it) }
|
|
182
|
+
account?.profileNeedsAgreement?.let { profile.putBoolean("profileNeedsAgreement", it) }
|
|
183
|
+
account?.profileNicknameNeedsAgreement?.let { profile.putBoolean("profileNicknameNeedsAgreement", it) }
|
|
184
|
+
account?.profileImageNeedsAgreement?.let { profile.putBoolean("profileImageNeedsAgreement", it) }
|
|
185
|
+
account?.nameNeedsAgreement?.let { profile.putBoolean("nameNeedsAgreement", it) }
|
|
186
|
+
account?.ciNeedsAgreement?.let { profile.putBoolean("ciNeedsAgreement", it) }
|
|
187
|
+
promise.resolve(profile)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 배송지 조회
|
|
192
|
+
@ReactMethod
|
|
193
|
+
override fun shippingAddresses(promise: Promise) {
|
|
194
|
+
UserApiClient.instance.shippingAddresses { addresses, error ->
|
|
195
|
+
if (error != null) {
|
|
196
|
+
promise.reject("E_FAILED_OPERATION", error.message, error)
|
|
197
|
+
return@shippingAddresses
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (addresses == null) {
|
|
201
|
+
promise.reject("E_SHIPPING_ADDRESSES_NOT_FOUND", "Shipping addresses not found")
|
|
202
|
+
return@shippingAddresses
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
val result = Arguments.createMap()
|
|
206
|
+
result.putString("userId", addresses.userId?.toString())
|
|
207
|
+
addresses.needsAgreement?.let { result.putBoolean("needsAgreement", it) }
|
|
208
|
+
|
|
209
|
+
val array = Arguments.createArray()
|
|
210
|
+
addresses.shippingAddresses?.map { addr ->
|
|
211
|
+
Arguments.createMap().apply {
|
|
212
|
+
putString("id", addr.id?.toString())
|
|
213
|
+
putString("name", addr.name)
|
|
214
|
+
addr.isDefault?.let { putBoolean("isDefault", it) }
|
|
215
|
+
putString("updatedAt", formatDate(addr.updatedAt))
|
|
216
|
+
putString("type", addr.type?.toString())
|
|
217
|
+
putString("baseAddress", addr.baseAddress)
|
|
218
|
+
putString("detailAddress", addr.detailAddress)
|
|
219
|
+
putString("receiverName", addr.receiverName)
|
|
220
|
+
putString("receiverPhoneNumber1", addr.receiverPhoneNumber1)
|
|
221
|
+
putString("receiverPhoneNumber2", addr.receiverPhoneNumber2)
|
|
222
|
+
putString("zoneNumber", addr.zoneNumber)
|
|
223
|
+
putString("zipCode", addr.zipCode)
|
|
224
|
+
}
|
|
225
|
+
}?.forEach(array::pushMap)
|
|
226
|
+
result.putArray("shippingAddresses", array)
|
|
227
|
+
|
|
228
|
+
promise.resolve(result)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 서비스 약관 조회
|
|
233
|
+
@ReactMethod
|
|
234
|
+
override fun serviceTerms(promise: Promise) {
|
|
235
|
+
UserApiClient.instance.serviceTerms { terms, error ->
|
|
236
|
+
if (error != null) {
|
|
237
|
+
promise.reject("E_FAILED_OPERATION", error.message, error)
|
|
238
|
+
return@serviceTerms
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
val result = Arguments.createMap()
|
|
242
|
+
terms?.id?.let { result.putString("userId", it.toString()) }
|
|
243
|
+
|
|
244
|
+
val array = Arguments.createArray()
|
|
245
|
+
terms?.serviceTerms?.map { term ->
|
|
246
|
+
Arguments.createMap().apply {
|
|
247
|
+
putString("tag", term.tag)
|
|
248
|
+
putBoolean("agreed", term.agreed)
|
|
249
|
+
putBoolean("required", term.required)
|
|
250
|
+
putBoolean("revocable", term.revocable)
|
|
251
|
+
term.agreedAt?.let { putString("agreedAt", formatDate(it)) }
|
|
252
|
+
}
|
|
253
|
+
}?.forEach(array::pushMap)
|
|
254
|
+
if (array.size() > 0) {
|
|
255
|
+
result.putArray("serviceTerms", array)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
promise.resolve(result)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 카카오계정 웹 로그인
|
|
263
|
+
private fun loginWithAccount(promise: Promise) {
|
|
264
|
+
val activity = currentActivity
|
|
265
|
+
|
|
266
|
+
if (activity == null) {
|
|
267
|
+
promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist")
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
UserApiClient.instance.loginWithKakaoAccount(activity) { token, error ->
|
|
272
|
+
when {
|
|
273
|
+
token != null -> promise.resolve(resolveToken(token.accessToken, token.refreshToken, token.idToken, token.scopes))
|
|
274
|
+
error != null -> promise.reject(resolveErrorCode(error), error.message, error)
|
|
275
|
+
else -> promise.reject("E_UNKNOWN_ERROR", "Login failed")
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 앱 메타데이터 조회
|
|
281
|
+
private fun resolveMetaData(key: String): String? {
|
|
282
|
+
return try {
|
|
283
|
+
val packageInfo = reactApplicationContext.packageManager.getApplicationInfo(
|
|
284
|
+
reactApplicationContext.packageName,
|
|
285
|
+
PackageManager.GET_META_DATA
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
packageInfo.metaData?.getString(key)
|
|
289
|
+
} catch (_: Exception) {
|
|
290
|
+
null
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 문자열 리소스 조회
|
|
295
|
+
private fun resolveString(name: String): String? {
|
|
296
|
+
val resourceId = reactApplicationContext.resources.getIdentifier(
|
|
297
|
+
name,
|
|
298
|
+
"string",
|
|
299
|
+
reactApplicationContext.packageName
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if (resourceId == 0) {
|
|
303
|
+
return null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return reactApplicationContext.getString(resourceId)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 토큰 응답 생성
|
|
310
|
+
private fun resolveToken(accessToken: String?, refreshToken: String?, idToken: String? = null, scopes: List<String>? = null): WritableMap {
|
|
311
|
+
val token = Arguments.createMap()
|
|
312
|
+
val current = TokenManagerProvider.instance.manager.getToken()
|
|
313
|
+
|
|
314
|
+
token.putString("accessToken", accessToken)
|
|
315
|
+
token.putString("refreshToken", refreshToken)
|
|
316
|
+
token.putString("idToken", idToken)
|
|
317
|
+
token.putString("accessTokenExpiresAt", formatDate(current?.accessTokenExpiresAt))
|
|
318
|
+
token.putString("refreshTokenExpiresAt", formatDate(current?.refreshTokenExpiresAt))
|
|
319
|
+
token.putString("appKeyHash", Utility.getKeyHash(reactApplicationContext))
|
|
320
|
+
|
|
321
|
+
val scopeArray = Arguments.createArray()
|
|
322
|
+
scopes?.forEach { scopeArray.pushString(it) }
|
|
323
|
+
token.putArray("scopes", scopeArray)
|
|
324
|
+
|
|
325
|
+
return token
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 에러 코드 변환
|
|
329
|
+
private fun resolveErrorCode(error: Throwable): String {
|
|
330
|
+
return when {
|
|
331
|
+
error is ClientError && error.reason == ClientErrorCause.Cancelled ->
|
|
332
|
+
"E_CANCELLED_OPERATION"
|
|
333
|
+
else -> "E_FAILED_OPERATION"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 날짜 포맷 변환
|
|
338
|
+
private fun formatDate(date: Date?): String? {
|
|
339
|
+
if (date == null) {
|
|
340
|
+
return null
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
|
|
344
|
+
formatter.timeZone = TimeZone.getTimeZone("UTC")
|
|
345
|
+
return formatter.format(date)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package com.packagekr.kakao
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
8
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
9
|
+
import com.facebook.react.uimanager.ViewManager
|
|
10
|
+
|
|
11
|
+
class RNKakaoSigninPackage : BaseReactPackage() {
|
|
12
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
13
|
+
return when (name) {
|
|
14
|
+
RNKakaoSigninModule.NAME -> RNKakaoSigninModule(reactContext)
|
|
15
|
+
else -> null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 모듈 정보 생성
|
|
20
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
21
|
+
val moduleList = arrayOf(RNKakaoSigninModule::class.java)
|
|
22
|
+
val reactModuleInfoMap = HashMap<String, ReactModuleInfo>()
|
|
23
|
+
|
|
24
|
+
for (moduleClass in moduleList) {
|
|
25
|
+
val reactModule = moduleClass.getAnnotation(ReactModule::class.java) ?: continue
|
|
26
|
+
|
|
27
|
+
reactModuleInfoMap[reactModule.name] = ReactModuleInfo(
|
|
28
|
+
reactModule.name,
|
|
29
|
+
moduleClass.name,
|
|
30
|
+
false,
|
|
31
|
+
reactModule.needsEagerInit,
|
|
32
|
+
reactModule.isCxxModule,
|
|
33
|
+
true
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return ReactModuleInfoProvider { reactModuleInfoMap }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 뷰 매니저 반환
|
|
41
|
+
override fun createViewManagers(
|
|
42
|
+
reactContext: ReactApplicationContext
|
|
43
|
+
): List<ViewManager<*, *>> {
|
|
44
|
+
return emptyList()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
import KakaoSDKCommon
|
|
4
|
+
import KakaoSDKAuth
|
|
5
|
+
|
|
6
|
+
enum RNKakaoError {
|
|
7
|
+
// 로그인 에러 메시지 보정
|
|
8
|
+
private static func resolveAuthMessage(
|
|
9
|
+
_ reason: AuthFailureReason,
|
|
10
|
+
_ authErrorInfo: AuthErrorInfo?,
|
|
11
|
+
_ fallback: String
|
|
12
|
+
) -> (code: String, message: String) {
|
|
13
|
+
let description = authErrorInfo?.errorDescription?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
14
|
+
let normalizedDescription = description?.lowercased() ?? ""
|
|
15
|
+
|
|
16
|
+
switch reason {
|
|
17
|
+
case .InvalidRequest:
|
|
18
|
+
if normalizedDescription.contains("bundle") {
|
|
19
|
+
return ("KAKAO_INVALID_BUNDLE_ID", "iOS 번들 ID 설정이 올바르지 않습니다. Kakao 콘솔의 iOS 번들 ID와 Xcode 설정을 확인해주세요.")
|
|
20
|
+
}
|
|
21
|
+
if normalizedDescription.contains("scheme") || normalizedDescription.contains("redirect") {
|
|
22
|
+
return ("KAKAO_INVALID_URL_SCHEME", "iOS URL scheme 설정이 올바르지 않습니다. Info.plist의 URL Types, KAKAO_APP_SCHEME, Kakao 콘솔 설정을 확인해주세요.")
|
|
23
|
+
}
|
|
24
|
+
if normalizedDescription.contains("client") || normalizedDescription.contains("app key") {
|
|
25
|
+
return ("KAKAO_INVALID_APP_KEY", "KAKAO_APP_KEY 값이 올바르지 않습니다. Kakao 네이티브 앱 키와 Info.plist 설정을 확인해주세요.")
|
|
26
|
+
}
|
|
27
|
+
return ("KAKAO_INVALID_REQUEST", "로그인 요청이 올바르지 않습니다. Kakao iOS 앱 설정과 전달 파라미터를 확인해주세요.")
|
|
28
|
+
|
|
29
|
+
case .InvalidClient:
|
|
30
|
+
return ("KAKAO_INVALID_CLIENT", "카카오 iOS 앱 설정이 올바르지 않습니다. KAKAO_APP_KEY, 번들 ID, iOS 플랫폼 등록값을 확인해주세요.")
|
|
31
|
+
|
|
32
|
+
case .AccessDenied:
|
|
33
|
+
return ("KAKAO_ACCESS_DENIED", "사용자가 카카오 로그인을 취소했거나 접근 권한을 거부했습니다.")
|
|
34
|
+
|
|
35
|
+
case .InvalidScope:
|
|
36
|
+
return ("KAKAO_INVALID_SCOPE", "유효하지 않은 동의 항목(scope)입니다. 요청한 동의 항목과 Kakao 콘솔 설정을 확인해주세요.")
|
|
37
|
+
|
|
38
|
+
case .Misconfigured:
|
|
39
|
+
return ("KAKAO_MISCONFIGURED", "카카오 iOS 설정이 올바르지 않습니다. 번들 ID, URL scheme, KAKAO_APP_KEY, Kakao 콘솔 설정을 확인해주세요.")
|
|
40
|
+
|
|
41
|
+
case .Unauthorized:
|
|
42
|
+
return ("KAKAO_UNAUTHORIZED", "카카오 로그인 권한이 없습니다. 앱 권한과 플랫폼 설정을 확인해주세요.")
|
|
43
|
+
|
|
44
|
+
case .InvalidGrant:
|
|
45
|
+
return ("KAKAO_INVALID_GRANT", "인가 정보가 유효하지 않습니다. 다시 로그인해주세요.")
|
|
46
|
+
|
|
47
|
+
case .ServerError:
|
|
48
|
+
return ("KAKAO_SERVER_ERROR", "카카오 서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.")
|
|
49
|
+
|
|
50
|
+
default:
|
|
51
|
+
return ("KAKAO_AUTH_ERROR", description ?? authErrorInfo?.error.rawValue ?? fallback)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// SDK 에러 해석
|
|
56
|
+
static func parse(_ error: Error) -> (code: String, message: String) {
|
|
57
|
+
guard let sdkError = error as? SdkError else {
|
|
58
|
+
return ("KAKAO_ERROR", error.localizedDescription)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
switch sdkError {
|
|
62
|
+
// 클라이언트 에러
|
|
63
|
+
case .ClientFailed(let reason, let errorMessage):
|
|
64
|
+
switch reason {
|
|
65
|
+
case .Cancelled:
|
|
66
|
+
return ("KAKAO_CANCELLED", "사용자가 로그인을 취소했습니다.")
|
|
67
|
+
case .NotSupported:
|
|
68
|
+
return ("KAKAO_NOT_SUPPORTED", "지원하지 않는 기능입니다.")
|
|
69
|
+
case .BadParameter:
|
|
70
|
+
return ("KAKAO_BAD_PARAMETER", "잘못된 파라미터입니다. \(errorMessage ?? "")")
|
|
71
|
+
case .TokenNotFound:
|
|
72
|
+
return ("KAKAO_TOKEN_NOT_FOUND", "로그인이 필요합니다.")
|
|
73
|
+
case .IllegalState:
|
|
74
|
+
return ("KAKAO_ILLEGAL_STATE", "잘못된 상태입니다. \(errorMessage ?? "")")
|
|
75
|
+
default:
|
|
76
|
+
return ("KAKAO_CLIENT_ERROR", errorMessage ?? error.localizedDescription)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// API 에러
|
|
80
|
+
case .ApiFailed(let reason, let errorInfo):
|
|
81
|
+
switch reason {
|
|
82
|
+
case .InvalidAccessToken:
|
|
83
|
+
return ("KAKAO_UNAUTHORIZED", "인증이 만료되었습니다. 다시 로그인해주세요.")
|
|
84
|
+
case .Permission, .InsufficientScope:
|
|
85
|
+
return ("KAKAO_FORBIDDEN", "권한이 없습니다. 동의 항목을 확인해주세요.")
|
|
86
|
+
case .ApiLimitExceed:
|
|
87
|
+
return ("KAKAO_RATE_LIMIT", "요청이 너무 많습니다. 잠시 후 다시 시도해주세요.")
|
|
88
|
+
default:
|
|
89
|
+
return ("KAKAO_API_ERROR", errorInfo?.msg ?? error.localizedDescription)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 인증 에러
|
|
93
|
+
case .AuthFailed(let reason, let authErrorInfo):
|
|
94
|
+
return resolveAuthMessage(reason, authErrorInfo, error.localizedDescription)
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
return ("KAKAO_ERROR", error.localizedDescription)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <RNKakaoSigninSpec/RNKakaoSigninSpec.h>
|
|
3
|
+
|
|
4
|
+
@interface RCT_EXTERN_MODULE(RNKakaoSignin, NSObject)
|
|
5
|
+
|
|
6
|
+
RCT_EXTERN_METHOD(login:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject);
|
|
7
|
+
RCT_EXTERN_METHOD(loginWithKakaoAccount:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject);
|
|
8
|
+
RCT_EXTERN_METHOD(logout:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject);
|
|
9
|
+
RCT_EXTERN_METHOD(unlink:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject);
|
|
10
|
+
RCT_EXTERN_METHOD(getProfile:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject);
|
|
11
|
+
RCT_EXTERN_METHOD(getAccessToken:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject);
|
|
12
|
+
|
|
13
|
+
@end
|