@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,256 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
import KakaoSDKCommon
|
|
4
|
+
import KakaoSDKAuth
|
|
5
|
+
import KakaoSDKUser
|
|
6
|
+
|
|
7
|
+
@objc(RNKakaoSignin)
|
|
8
|
+
class RNKakaoSignin: NSObject {
|
|
9
|
+
|
|
10
|
+
// SDK 초기화
|
|
11
|
+
public override init() {
|
|
12
|
+
let appKey = Bundle.main.object(forInfoDictionaryKey: "KAKAO_APP_KEY") as? String
|
|
13
|
+
let customScheme = Bundle.main.object(forInfoDictionaryKey: "KAKAO_APP_SCHEME") as? String
|
|
14
|
+
|
|
15
|
+
RNKakaoSignin.configureSDK(appKey: appKey, customScheme: customScheme)
|
|
16
|
+
|
|
17
|
+
super.init()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 메인 큐 초기화
|
|
21
|
+
@objc static func requiresMainQueueSetup() -> Bool { true }
|
|
22
|
+
|
|
23
|
+
// 카카오톡 로그인 URL 확인
|
|
24
|
+
@objc(isKakaoTalkLoginUrl:)
|
|
25
|
+
static func isKakaoTalkLoginUrl(_ url: URL) -> Bool {
|
|
26
|
+
return AuthApi.isKakaoTalkLoginUrl(url)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 카카오톡 로그인 URL 처리
|
|
30
|
+
@objc(handleOpenUrl:)
|
|
31
|
+
static func handleOpenUrl(_ url: URL) -> Bool {
|
|
32
|
+
return AuthController.handleOpenUrl(url: url)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 카카오 로그인
|
|
36
|
+
@objc(login:rejecter:)
|
|
37
|
+
func login(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
38
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
39
|
+
runOnMain {
|
|
40
|
+
let completion = self.tokenHandler(resolve, reject)
|
|
41
|
+
|
|
42
|
+
if UserApi.isKakaoTalkLoginAvailable() {
|
|
43
|
+
UserApi.shared.loginWithKakaoTalk(completion: completion)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
UserApi.shared.loginWithKakaoAccount(completion: completion)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 카카오계정 로그인
|
|
52
|
+
@objc(loginWithKakaoAccount:rejecter:)
|
|
53
|
+
func loginWithKakaoAccount(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
54
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
55
|
+
runOnMain { UserApi.shared.loginWithKakaoAccount(completion: self.tokenHandler(resolve, reject)) }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 세션 처리
|
|
59
|
+
|
|
60
|
+
// 로그아웃
|
|
61
|
+
@objc(logout:rejecter:)
|
|
62
|
+
func logout(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
63
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
64
|
+
runOnMain { UserApi.shared.logout(completion: self.messageHandler(resolve, reject, "Successfully logged out")) }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 연결 끊기
|
|
68
|
+
@objc(unlink:rejecter:)
|
|
69
|
+
func unlink(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
70
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
71
|
+
runOnMain { UserApi.shared.unlink(completion: self.messageHandler(resolve, reject, "Successfully unlinked")) }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 토큰 정보 조회
|
|
75
|
+
@objc(getAccessToken:rejecter:)
|
|
76
|
+
func getAccessToken(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
77
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
78
|
+
runOnMain {
|
|
79
|
+
UserApi.shared.accessTokenInfo { info, error in
|
|
80
|
+
if let error = error { self.reject(reject, error) }
|
|
81
|
+
else {
|
|
82
|
+
resolve([
|
|
83
|
+
"accessToken": TokenManager.manager.getToken()?.accessToken as Any,
|
|
84
|
+
"expiresIn": info?.expiresIn as Any,
|
|
85
|
+
])
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 프로필 조회
|
|
92
|
+
@objc(getProfile:rejecter:)
|
|
93
|
+
func getProfile(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
94
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
95
|
+
runOnMain {
|
|
96
|
+
UserApi.shared.me { user, error in
|
|
97
|
+
if let error = error { self.reject(reject, error) }
|
|
98
|
+
else {
|
|
99
|
+
let account = user?.kakaoAccount
|
|
100
|
+
let profile = account?.profile
|
|
101
|
+
resolve([
|
|
102
|
+
"id": user?.id as Any,
|
|
103
|
+
"name": account?.name as Any,
|
|
104
|
+
"email": account?.email as Any,
|
|
105
|
+
"nickname": profile?.nickname as Any,
|
|
106
|
+
"profileImageUrl": profile?.profileImageUrl?.absoluteString as Any,
|
|
107
|
+
"thumbnailImageUrl": profile?.thumbnailImageUrl?.absoluteString as Any,
|
|
108
|
+
"phoneNumber": account?.phoneNumber as Any,
|
|
109
|
+
"ageRange": account?.ageRange?.rawValue as Any,
|
|
110
|
+
"birthday": account?.birthday as Any,
|
|
111
|
+
"birthdayType": account?.birthdayType as Any,
|
|
112
|
+
"birthyear": account?.birthyear as Any,
|
|
113
|
+
"gender": account?.gender?.rawValue as Any,
|
|
114
|
+
"isEmailValid": account?.isEmailValid as Any,
|
|
115
|
+
"isEmailVerified": account?.isEmailVerified as Any,
|
|
116
|
+
"isKorean": account?.isKorean as Any,
|
|
117
|
+
"hasEmail": account?.hasEmail as Any,
|
|
118
|
+
"hasPhoneNumber": account?.hasPhoneNumber as Any,
|
|
119
|
+
"hasBirthday": account?.hasBirthday as Any,
|
|
120
|
+
"hasBirthyear": account?.hasBirthyear as Any,
|
|
121
|
+
"hasAgeRange": account?.hasAgeRange as Any,
|
|
122
|
+
"hasGender": account?.hasGender as Any,
|
|
123
|
+
"isDefaultImage": profile?.isDefaultImage as Any,
|
|
124
|
+
"isDefaultNickname": profile?.isDefaultNickname as Any,
|
|
125
|
+
"connectedAt": user?.connectedAt as Any,
|
|
126
|
+
"synchedAt": user?.synchedAt as Any,
|
|
127
|
+
"isLeapMonth": account?.isLeapMonth as Any,
|
|
128
|
+
"ci": account?.ci as Any,
|
|
129
|
+
"ciAuthenticatedAt": account?.ciAuthenticatedAt as Any,
|
|
130
|
+
"ageRangeNeedsAgreement": account?.ageRangeNeedsAgreement as Any,
|
|
131
|
+
"birthdayNeedsAgreement": account?.birthdayNeedsAgreement as Any,
|
|
132
|
+
"birthyearNeedsAgreement": account?.birthyearNeedsAgreement as Any,
|
|
133
|
+
"emailNeedsAgreement": account?.emailNeedsAgreement as Any,
|
|
134
|
+
"genderNeedsAgreement": account?.genderNeedsAgreement as Any,
|
|
135
|
+
"isKoreanNeedsAgreement": account?.isKoreanNeedsAgreement as Any,
|
|
136
|
+
"phoneNumberNeedsAgreement": account?.phoneNumberNeedsAgreement as Any,
|
|
137
|
+
"profileNeedsAgreement": account?.profileNeedsAgreement as Any,
|
|
138
|
+
"profileNicknameNeedsAgreement": account?.profileNicknameNeedsAgreement as Any,
|
|
139
|
+
"profileImageNeedsAgreement": account?.profileImageNeedsAgreement as Any,
|
|
140
|
+
"nameNeedsAgreement": account?.nameNeedsAgreement as Any,
|
|
141
|
+
"ciNeedsAgreement": account?.ciNeedsAgreement as Any,
|
|
142
|
+
])
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 배송지 조회
|
|
149
|
+
@objc(shippingAddresses:rejecter:)
|
|
150
|
+
func shippingAddresses(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
151
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
152
|
+
runOnMain {
|
|
153
|
+
let fmt = RNKakaoSigninHelper.dateFormatter
|
|
154
|
+
UserApi.shared.shippingAddresses { addresses, error in
|
|
155
|
+
if let error = error { self.reject(reject, error) }
|
|
156
|
+
else {
|
|
157
|
+
resolve([
|
|
158
|
+
"userId": addresses?.userId as Any,
|
|
159
|
+
"needsAgreement": addresses?.needsAgreement as Any,
|
|
160
|
+
"shippingAddresses": addresses?.shippingAddresses?.map { addr in [
|
|
161
|
+
"id": addr.id as Any,
|
|
162
|
+
"name": addr.name as Any,
|
|
163
|
+
"isDefault": addr.isDefault as Any,
|
|
164
|
+
"updatedAt": fmt.string(from: addr.updatedAt!) as Any,
|
|
165
|
+
"type": addr.type?.rawValue as Any,
|
|
166
|
+
"baseAddress": addr.baseAddress as Any,
|
|
167
|
+
"detailAddress": addr.detailAddress as Any,
|
|
168
|
+
"receiverName": addr.receiverName as Any,
|
|
169
|
+
"receiverPhoneNumber1": addr.receiverPhoneNumber1 as Any,
|
|
170
|
+
"receiverPhoneNumber2": addr.receiverPhoneNumber2 as Any,
|
|
171
|
+
"zoneNumber": addr.zoneNumber as Any,
|
|
172
|
+
"zipCode": addr.zipCode as Any,
|
|
173
|
+
]} as Any,
|
|
174
|
+
])
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 서비스 약관 조회
|
|
181
|
+
@objc(serviceTerms:rejecter:)
|
|
182
|
+
func serviceTerms(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
183
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
184
|
+
runOnMain {
|
|
185
|
+
let fmt = RNKakaoSigninHelper.dateFormatter
|
|
186
|
+
UserApi.shared.serviceTerms { terms, error in
|
|
187
|
+
if let error = error { self.reject(reject, error) }
|
|
188
|
+
else {
|
|
189
|
+
var result: [String: Any] = [:]
|
|
190
|
+
if let userId = terms?.id { result["userId"] = userId }
|
|
191
|
+
if let serviceTerms = terms?.serviceTerms {
|
|
192
|
+
result["serviceTerms"] = serviceTerms.map { term -> [String: Any] in
|
|
193
|
+
var dict: [String: Any] = [
|
|
194
|
+
"tag": term.tag,
|
|
195
|
+
"agreed": term.agreed,
|
|
196
|
+
"required": term.required,
|
|
197
|
+
"revocable": term.revocable,
|
|
198
|
+
]
|
|
199
|
+
if let agreedAt = term.agreedAt {
|
|
200
|
+
dict["agreedAt"] = fmt.string(from: agreedAt)
|
|
201
|
+
}
|
|
202
|
+
return dict
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
resolve(result)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// SDK 설정
|
|
212
|
+
private static func configureSDK(appKey: String?, customScheme: String?) {
|
|
213
|
+
guard let appKey = appKey else { return }
|
|
214
|
+
|
|
215
|
+
if let customScheme = customScheme {
|
|
216
|
+
KakaoSDK.initSDK(appKey: appKey, customScheme: customScheme)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
KakaoSDK.initSDK(appKey: appKey)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 메인 스레드 실행
|
|
224
|
+
private func runOnMain(_ action: @escaping () -> Void) {
|
|
225
|
+
DispatchQueue.main.async(execute: action)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 토큰 응답 콜백
|
|
229
|
+
private func tokenHandler(
|
|
230
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
231
|
+
_ reject: @escaping RCTPromiseRejectBlock
|
|
232
|
+
) -> (OAuthToken?, Error?) -> Void {
|
|
233
|
+
return { token, error in
|
|
234
|
+
if let error = error { self.reject(reject, error) }
|
|
235
|
+
else { resolve(RNKakaoSigninHelper.tokenToDict(token)) }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 메시지 응답 콜백
|
|
240
|
+
private func messageHandler(
|
|
241
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
242
|
+
_ reject: @escaping RCTPromiseRejectBlock,
|
|
243
|
+
_ message: String
|
|
244
|
+
) -> (Error?) -> Void {
|
|
245
|
+
return { error in
|
|
246
|
+
if let error = error { self.reject(reject, error) }
|
|
247
|
+
else { resolve(message) }
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 에러 변환
|
|
252
|
+
private func reject(_ reject: RCTPromiseRejectBlock, _ error: Error) {
|
|
253
|
+
let (code, message) = RNKakaoError.parse(error)
|
|
254
|
+
reject(code, message, error)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
import KakaoSDKAuth
|
|
4
|
+
|
|
5
|
+
// 공통 헬퍼
|
|
6
|
+
enum RNKakaoSigninHelper {
|
|
7
|
+
|
|
8
|
+
static let dateFormatter: DateFormatter = {
|
|
9
|
+
let f = DateFormatter()
|
|
10
|
+
f.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
11
|
+
return f
|
|
12
|
+
}()
|
|
13
|
+
|
|
14
|
+
static func tokenToDict(_ token: OAuthToken?) -> [String: Any] {
|
|
15
|
+
guard let token = token else { return [:] }
|
|
16
|
+
return [
|
|
17
|
+
"accessToken": token.accessToken,
|
|
18
|
+
"refreshToken": token.refreshToken,
|
|
19
|
+
"idToken": token.idToken as Any,
|
|
20
|
+
"accessTokenExpiresAt": dateFormatter.string(from: token.expiredAt),
|
|
21
|
+
"refreshTokenExpiresAt": dateFormatter.string(from: token.refreshTokenExpiredAt),
|
|
22
|
+
"scopes": token.scopes as Any,
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
#import <objc/runtime.h>
|
|
3
|
+
|
|
4
|
+
// 카카오 로그인 URL을 체크하는 Swift 클래스
|
|
5
|
+
@interface RNKakaoSignin : NSObject
|
|
6
|
+
+ (BOOL)isKakaoTalkLoginUrl:(NSURL *)url;
|
|
7
|
+
+ (BOOL)handleOpenUrl:(NSURL *)url;
|
|
8
|
+
@end
|
|
9
|
+
|
|
10
|
+
typedef BOOL (*RNKakaoOpenURLIMP)(id, SEL, UIApplication *, NSURL *, NSDictionary *);
|
|
11
|
+
typedef BOOL (*RNKakaoContinueUserActivityIMP)(id, SEL, UIApplication *, NSUserActivity *, void (^)(NSArray<id<UIUserActivityRestoring>> *));
|
|
12
|
+
typedef void (*RNKakaoSetOriginalIMP)(IMP imp);
|
|
13
|
+
|
|
14
|
+
static RNKakaoOpenURLIMP rnKakaoOriginalOpenURLIMP = NULL;
|
|
15
|
+
static RNKakaoContinueUserActivityIMP rnKakaoOriginalContinueUserActivityIMP = NULL;
|
|
16
|
+
|
|
17
|
+
static void RNKakaoStoreOpenURLIMP(IMP imp);
|
|
18
|
+
static void RNKakaoStoreUserActivityIMP(IMP imp);
|
|
19
|
+
static BOOL RNKakaoHandleURL(NSURL *url);
|
|
20
|
+
static void RNKakaoInstallHandler(Class cls, SEL selector, IMP interceptor, const char *types, RNKakaoSetOriginalIMP storeOriginal, NSString *name);
|
|
21
|
+
|
|
22
|
+
// 카카오 로그인 복귀 URL 처리
|
|
23
|
+
static BOOL RNKakaoHandleURL(NSURL *url) {
|
|
24
|
+
if (url == nil) {
|
|
25
|
+
return NO;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if ([RNKakaoSignin isKakaoTalkLoginUrl:url]) {
|
|
29
|
+
return [RNKakaoSignin handleOpenUrl:url];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return NO;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// openURL 복귀 처리
|
|
36
|
+
static BOOL RNKakaoSignin_openURL(id self, SEL _cmd, UIApplication *app, NSURL *url, NSDictionary *options) {
|
|
37
|
+
NSLog(@"[RNKakaoSignin] openURL received: %@", url.absoluteString);
|
|
38
|
+
|
|
39
|
+
if (RNKakaoHandleURL(url)) {
|
|
40
|
+
return YES;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (rnKakaoOriginalOpenURLIMP != NULL) {
|
|
44
|
+
return rnKakaoOriginalOpenURLIMP(self, _cmd, app, url, options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return NO;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// universal link 복귀 처리
|
|
51
|
+
static BOOL RNKakaoSignin_continueUserActivity(
|
|
52
|
+
id self,
|
|
53
|
+
SEL _cmd,
|
|
54
|
+
UIApplication *app,
|
|
55
|
+
NSUserActivity *userActivity,
|
|
56
|
+
void (^restorationHandler)(NSArray<id<UIUserActivityRestoring>> *)
|
|
57
|
+
) {
|
|
58
|
+
NSURL *url = userActivity.webpageURL;
|
|
59
|
+
NSLog(@"[RNKakaoSignin] continueUserActivity received: %@", url.absoluteString);
|
|
60
|
+
|
|
61
|
+
if (RNKakaoHandleURL(url)) {
|
|
62
|
+
return YES;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (rnKakaoOriginalContinueUserActivityIMP != NULL) {
|
|
66
|
+
return rnKakaoOriginalContinueUserActivityIMP(self, _cmd, app, userActivity, restorationHandler);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return NO;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 기존 openURL 저장
|
|
73
|
+
static void RNKakaoStoreOpenURLIMP(IMP imp) {
|
|
74
|
+
rnKakaoOriginalOpenURLIMP = (RNKakaoOpenURLIMP)imp;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 기존 continueUserActivity 저장
|
|
78
|
+
static void RNKakaoStoreUserActivityIMP(IMP imp) {
|
|
79
|
+
rnKakaoOriginalContinueUserActivityIMP = (RNKakaoContinueUserActivityIMP)imp;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// delegate 메서드 주입
|
|
83
|
+
static void RNKakaoInstallHandler(
|
|
84
|
+
Class cls,
|
|
85
|
+
SEL selector,
|
|
86
|
+
IMP interceptor,
|
|
87
|
+
const char *types,
|
|
88
|
+
RNKakaoSetOriginalIMP storeOriginal,
|
|
89
|
+
NSString *name
|
|
90
|
+
) {
|
|
91
|
+
Method method = class_getInstanceMethod(cls, selector);
|
|
92
|
+
|
|
93
|
+
if (method == NULL) {
|
|
94
|
+
class_addMethod(cls, selector, interceptor, types);
|
|
95
|
+
NSLog(@"[RNKakaoSignin] %@ added to %@", name, NSStringFromClass(cls));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
storeOriginal(method_getImplementation(method));
|
|
100
|
+
method_setImplementation(method, interceptor);
|
|
101
|
+
NSLog(@"[RNKakaoSignin] %@ swizzled on %@", name, NSStringFromClass(cls));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 로더
|
|
105
|
+
@interface RNKakaoSigninLoader : NSObject
|
|
106
|
+
@end
|
|
107
|
+
|
|
108
|
+
@implementation RNKakaoSigninLoader
|
|
109
|
+
|
|
110
|
+
// 앱 delegate 주입
|
|
111
|
+
+ (void)load {
|
|
112
|
+
int classCount = objc_getClassList(NULL, 0);
|
|
113
|
+
if (classCount <= 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * (NSUInteger)classCount);
|
|
118
|
+
classCount = objc_getClassList(classes, classCount);
|
|
119
|
+
|
|
120
|
+
// UIApplicationDelegate 채택 클래스 주입
|
|
121
|
+
for (int i = 0; i < classCount; i += 1) {
|
|
122
|
+
Class cls = classes[i];
|
|
123
|
+
if (!class_conformsToProtocol(cls, @protocol(UIApplicationDelegate))) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
RNKakaoInstallHandler(
|
|
128
|
+
cls,
|
|
129
|
+
@selector(application:openURL:options:),
|
|
130
|
+
(IMP)RNKakaoSignin_openURL,
|
|
131
|
+
"B@:@@@",
|
|
132
|
+
RNKakaoStoreOpenURLIMP,
|
|
133
|
+
@"openURL"
|
|
134
|
+
);
|
|
135
|
+
RNKakaoInstallHandler(
|
|
136
|
+
cls,
|
|
137
|
+
@selector(application:continueUserActivity:restorationHandler:),
|
|
138
|
+
(IMP)RNKakaoSignin_continueUserActivity,
|
|
139
|
+
"B@:@@@@",
|
|
140
|
+
RNKakaoStoreUserActivityIMP,
|
|
141
|
+
@"continueUserActivity"
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
free(classes);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
# RCT_NEW_ARCH_ENABLED = 1일경우, install_modules_dependencies(s) 호출
|
|
4
|
+
fabric_enabled = ENV["RCT_NEW_ARCH_ENABLED"] == "1"
|
|
5
|
+
|
|
6
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
7
|
+
kakao_sdk_version = "2.22.0"
|
|
8
|
+
|
|
9
|
+
Pod::Spec.new do |s|
|
|
10
|
+
s.name = "kakao-login"
|
|
11
|
+
s.version = package["version"]
|
|
12
|
+
s.summary = package["description"]
|
|
13
|
+
s.homepage = "https://github.com/Package-KR/react-native-kakao-signin"
|
|
14
|
+
s.license = "MIT"
|
|
15
|
+
s.authors = { "Package.kr" => "" }
|
|
16
|
+
s.platforms = { :ios => "13.0" }
|
|
17
|
+
s.framework = 'UIKit'
|
|
18
|
+
s.source = { :git => "https://github.com/Package-KR/react-native-kakao-signin.git", :tag => "#{s.version}" }
|
|
19
|
+
|
|
20
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
21
|
+
s.requires_arc = true
|
|
22
|
+
|
|
23
|
+
if fabric_enabled
|
|
24
|
+
install_modules_dependencies(s)
|
|
25
|
+
else
|
|
26
|
+
s.dependency "React-Core"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if defined?($KakaoSDKVersion)
|
|
30
|
+
kakao_sdk_version = $KakaoSDKVersion
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
s.dependency 'KakaoSDKCommon', kakao_sdk_version
|
|
34
|
+
s.dependency 'KakaoSDKAuth', kakao_sdk_version
|
|
35
|
+
s.dependency 'KakaoSDKUser', kakao_sdk_version
|
|
36
|
+
end
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@package-kr/react-native-kakao-signin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React Native Kakao Login",
|
|
5
|
+
"main": "src/index",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"android": "npm run android --workspace KakaoLoginExample",
|
|
9
|
+
"ios": "npm run ios --workspace KakaoLoginExample --",
|
|
10
|
+
"lint": "npm run lint --workspaces --if-present",
|
|
11
|
+
"start": "npm run start --workspace KakaoLoginExample",
|
|
12
|
+
"test": "npm run test --workspaces --if-present"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Package-KR/react-native-kakao-signin"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"ios",
|
|
21
|
+
"android",
|
|
22
|
+
"*.podspec",
|
|
23
|
+
"!KakaoLoginExample",
|
|
24
|
+
"!android/build",
|
|
25
|
+
"!ios/build"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"react-native",
|
|
32
|
+
"kakao",
|
|
33
|
+
"login"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"workspaces": [
|
|
37
|
+
"KakaoLoginExample"
|
|
38
|
+
],
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "*",
|
|
41
|
+
"react-native": "*"
|
|
42
|
+
},
|
|
43
|
+
"codegenConfig": {
|
|
44
|
+
"name": "RNKakaoSigninSpec",
|
|
45
|
+
"type": "modules",
|
|
46
|
+
"jsSrcsDir": "src",
|
|
47
|
+
"android": {
|
|
48
|
+
"javaPackageName": "com.packagekr.kakao"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">= 22.11.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface Spec extends TurboModule {
|
|
5
|
+
login(): Promise<{ [key: string]: Object }>;
|
|
6
|
+
loginWithKakaoAccount(): Promise<{ [key: string]: Object }>;
|
|
7
|
+
logout(): Promise<string>;
|
|
8
|
+
unlink(): Promise<string>;
|
|
9
|
+
getProfile(): Promise<{ [key: string]: Object }>;
|
|
10
|
+
getAccessToken(): Promise<{ [key: string]: Object }>;
|
|
11
|
+
shippingAddresses(): Promise<{ [key: string]: Object }>;
|
|
12
|
+
serviceTerms(): Promise<{ [key: string]: Object }>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('RNKakaoSignin');
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import NativeKakaoLogin from './NativeKakaoLogin';
|
|
2
|
+
|
|
3
|
+
import type { KakaoOAuthToken, KakaoProfile, KakaoAccessTokenInfo, KakaoShippingAddresses, KakaoServiceTerms } from './types';
|
|
4
|
+
|
|
5
|
+
// 카카오 로그인
|
|
6
|
+
export const login = (): Promise<KakaoOAuthToken> => {
|
|
7
|
+
return NativeKakaoLogin.login() as unknown as Promise<KakaoOAuthToken>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// 카카오계정으로 로그인
|
|
11
|
+
export const loginWithKakaoAccount = (): Promise<KakaoOAuthToken> => {
|
|
12
|
+
return NativeKakaoLogin.loginWithKakaoAccount() as unknown as Promise<KakaoOAuthToken>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// 로그아웃
|
|
16
|
+
export const logout = (): Promise<string> => {
|
|
17
|
+
return NativeKakaoLogin.logout();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 연결 끊기
|
|
21
|
+
export const unlink = (): Promise<string> => {
|
|
22
|
+
return NativeKakaoLogin.unlink();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// 프로필 조회
|
|
26
|
+
export const getProfile = (): Promise<KakaoProfile> => {
|
|
27
|
+
return NativeKakaoLogin.getProfile() as unknown as Promise<KakaoProfile>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// 토큰 정보 조회
|
|
31
|
+
export const getAccessToken = (): Promise<KakaoAccessTokenInfo> => {
|
|
32
|
+
return NativeKakaoLogin.getAccessToken() as unknown as Promise<KakaoAccessTokenInfo>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 배송지 조회
|
|
36
|
+
export const shippingAddresses = (): Promise<KakaoShippingAddresses> => {
|
|
37
|
+
return NativeKakaoLogin.shippingAddresses() as unknown as Promise<KakaoShippingAddresses>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// 서비스 약관 조회
|
|
41
|
+
export const serviceTerms = (): Promise<KakaoServiceTerms> => {
|
|
42
|
+
return NativeKakaoLogin.serviceTerms() as unknown as Promise<KakaoServiceTerms>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export * from './types';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export type KakaoOAuthToken = {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
idToken: string | null;
|
|
5
|
+
accessTokenExpiresAt: string;
|
|
6
|
+
refreshTokenExpiresAt: string;
|
|
7
|
+
scopes: string[] | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type KakaoAccessTokenInfo = {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
expiresIn: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type KakaoShippingAddress = {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
isDefault: boolean;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
type: string;
|
|
21
|
+
baseAddress: string;
|
|
22
|
+
detailAddress: string;
|
|
23
|
+
receiverName: string;
|
|
24
|
+
receiverPhoneNumber1: string;
|
|
25
|
+
receiverPhoneNumber2: string;
|
|
26
|
+
zoneNumber: string;
|
|
27
|
+
zipCode: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type KakaoShippingAddresses = {
|
|
31
|
+
userId: string;
|
|
32
|
+
needsAgreement: boolean;
|
|
33
|
+
shippingAddresses: KakaoShippingAddress[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type KakaoServiceTerm = {
|
|
37
|
+
tag: string;
|
|
38
|
+
agreed: boolean;
|
|
39
|
+
required: boolean;
|
|
40
|
+
revocable: boolean;
|
|
41
|
+
agreedAt?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type KakaoServiceTerms = {
|
|
45
|
+
userId: string;
|
|
46
|
+
serviceTerms: KakaoServiceTerm[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type KakaoProfile = {
|
|
50
|
+
id: number | null;
|
|
51
|
+
nickname: string | null;
|
|
52
|
+
name: string | null;
|
|
53
|
+
email: string | null;
|
|
54
|
+
profileImageUrl: string | null;
|
|
55
|
+
thumbnailImageUrl: string | null;
|
|
56
|
+
gender: string | null;
|
|
57
|
+
ageRange: string | null;
|
|
58
|
+
birthday: string | null;
|
|
59
|
+
birthdayType: string | null;
|
|
60
|
+
birthyear: string | null;
|
|
61
|
+
phoneNumber: string | null;
|
|
62
|
+
isEmailValid: boolean | null;
|
|
63
|
+
isEmailVerified: boolean | null;
|
|
64
|
+
isKorean: boolean | null;
|
|
65
|
+
hasEmail: boolean | null;
|
|
66
|
+
hasPhoneNumber: boolean | null;
|
|
67
|
+
hasBirthday: boolean | null;
|
|
68
|
+
hasBirthyear: boolean | null;
|
|
69
|
+
hasAgeRange: boolean | null;
|
|
70
|
+
hasGender: boolean | null;
|
|
71
|
+
isDefaultImage: boolean | null;
|
|
72
|
+
isDefaultNickname: boolean | null;
|
|
73
|
+
connectedAt: string | null;
|
|
74
|
+
synchedAt: string | null;
|
|
75
|
+
isLeapMonth: boolean | null;
|
|
76
|
+
ci: string | null;
|
|
77
|
+
ciAuthenticatedAt: string | null;
|
|
78
|
+
emailNeedsAgreement: boolean | null;
|
|
79
|
+
profileNeedsAgreement: boolean | null;
|
|
80
|
+
phoneNumberNeedsAgreement: boolean | null;
|
|
81
|
+
genderNeedsAgreement: boolean | null;
|
|
82
|
+
ageRangeNeedsAgreement: boolean | null;
|
|
83
|
+
birthdayNeedsAgreement: boolean | null;
|
|
84
|
+
birthyearNeedsAgreement: boolean | null;
|
|
85
|
+
isKoreanNeedsAgreement: boolean | null;
|
|
86
|
+
profileNicknameNeedsAgreement: boolean | null;
|
|
87
|
+
profileImageNeedsAgreement: boolean | null;
|
|
88
|
+
nameNeedsAgreement: boolean | null;
|
|
89
|
+
ciNeedsAgreement: boolean | null;
|
|
90
|
+
};
|