├── .babelrc.js ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── @types └── index.d.ts ├── LICENSE ├── README.md ├── RNAKakaoSDK.podspec ├── android ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── io │ └── actbase │ └── kakaosdk │ ├── RNAKakaoSDK.java │ └── RNAKakaoSDKPackage.java ├── assets └── xcode_0501.png ├── ios ├── RNAKakaoSDK-Bridging-Header.h ├── RNAKakaoSDK.swift ├── RNAKakaoSDK.xcodeproj │ └── project.pbxproj ├── RNAKakaoSDKModule.h ├── RNAKakaoSDKModule.m ├── WithKakaoSDK.h └── WithKakaoSDK.m ├── package.json ├── src ├── app.native.ts ├── app.ts ├── index.ts └── types.ts ├── src_bin └── install └── tsconfig.json /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | api.cache(true); 3 | return { 4 | presets: [['@babel/preset-env']], 5 | plugins: [ 6 | '@babel/plugin-transform-react-jsx', 7 | '@babel/plugin-proposal-object-rest-spread', 8 | '@babel/plugin-proposal-optional-chaining', 9 | "inline-import-data-uri" 10 | ], 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | example/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parserOptions": { 3 | "ecmaVersion": 2018, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true, 7 | "modules": true, 8 | "experimentalObjectRestSpread": true 9 | } 10 | }, 11 | "env": { 12 | "browser" : true, 13 | "node": true 14 | }, 15 | "parser": "@typescript-eslint/parser", 16 | "plugins": ["react", "@typescript-eslint/eslint-plugin", "babel"], 17 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended"], 18 | "globals": { "Promise": true, "setTimeout": true, "FormData": true, "global": true, "document": true }, 19 | "rules": { 20 | "@typescript-eslint/camelcase": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | /node_modules 4 | /dist 5 | /lib 6 | /modules 7 | /bin 8 | 9 | .idea 10 | .watchmanconfig 11 | package-lock.json 12 | .next 13 | android/local.properties 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/ 3 | .git* 4 | src/ 5 | src_modules/ 6 | example/ 7 | docs/ 8 | assets/ 9 | 10 | tsconfig.json 11 | .babelrc.js 12 | .gitignore 13 | .npmignore 14 | .prettierrc.js 15 | yarn.lock 16 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: true, 4 | useTabs: false, 5 | tabWidth: 2, 6 | trailingComma: 'all', 7 | printWidth: 120, 8 | }; 9 | -------------------------------------------------------------------------------- /@types/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | interface Window { 3 | Kakao: any; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Creamcookie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KakaoSDK for React, React-Native 2 | 3 | ![platforms](https://img.shields.io/badge/platforms-Android%20%7C%20iOS%20%7C%20Web-brightgreen.svg?style=flat-square&colorB=191A17) 4 | [![npm](https://img.shields.io/npm/v/@actbase/react-kakaosdk.svg?style=flat-square)](https://www.npmjs.com/package/@actbase/react-kakaosdk) 5 | [![npm](https://img.shields.io/npm/dm/@actbase/react-kakaosdk.svg?style=flat-square&colorB=007ec6)](https://www.npmjs.com/package/@actbase/react-kakaosdk) 6 | [![github issues](https://img.shields.io/github/issues/actbase/react-kakaosdk.svg?style=flat-square)](https://github.com/actbase/react-kakaosdk/issues) 7 | [![github closed issues](https://img.shields.io/github/issues-closed/actbase/react-kakaosdk.svg?style=flat-square&colorB=44cc11)](https://github.com/actbase/react-kakaosdk/issues?q=is%3Aissue+is%3Aclosed) 8 | [![Issue Stats](https://img.shields.io/issuestats/i/github/actbase/react-kakaosdk.svg?style=flat-square&colorB=44cc11)](http://github.com/actbase/react-kakaosdk/issues) 9 | 10 | ## Use Dependencies 11 | 12 | | iOS | Android | Web | 13 | | --------------------------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------- | 14 | | [2.5.1](https://developers.kakao.com/docs/latest/ko/sdk-download/ios) | [2.5.0](https://developers.kakao.com/docs/latest/ko/sdk-download/android) | [1.39.14](https://developers.kakao.com/docs/latest/ko/sdk-download/js) | 15 | 16 | ### 처음 설치 시 주의 사항 (React-Native 만) 17 | 18 | 해당 모듈은 Swift로 되어있어서
19 | 그냥 가동 시 작동이 안될 수 있습니다. 20 | 21 | Xcode에서 프로젝트 내 비어있는 Swift File를 새로 만들고 Headers 생성을 누르면
22 | 스위프트 모드로 잡히면서 정상적으로 돌게 됩니다. 23 | 24 | 이후 해결방안 나오면 별도로 공지하겠습니다. 25 | 26 | ### Xcode 12.5 업뎃 후 빌드 실패 해결법 (React-Native 만) 27 | 28 | 갑자기 Xcode 업데이트 후 디버그로는 빌드가 안되는 문제가 있습니다.
29 | 원인은 Alamofire에서 나오는 부분인데 프로젝트 설정으로 해결할 수 있습니다. 30 | 31 | 해당 프로젝트 설정을 접근 후 Target에 프로젝트에서 32 | Build Settings 접근 후 Library Search Path를 검색하면 그 안에 Debug쪽에 있는 부분을 수정해야합니다. 33 | 34 | 35 | 36 | \$(inherited)를 제외한 2개를 삭제 후 저장하고 빌드하면 정상적으로 돌아갑니다. 37 | 38 | ## 사용 환경 39 | 40 | - CRA (create-react-app) 41 | - Next.js 42 | - React-Native 0.61 이상 43 | - React-Native-Web 44 | 45 | ## 시작하기 46 | 47 | ```bash 48 | $ npx @actbase/react-kakaosdk 49 | ``` 50 | 51 | 웹이나 앱을 구분 한 뒤 알아서 wizard가 실행됩니다. 52 | 53 | ### iOS 54 | 55 | [공식문서 - 개발 프로젝트 설정](https://developers.kakao.com/docs/latest/ko/getting-started/sdk-ios-v1) 을 참고하여 `info.plist` 의 아래`NATIVE_APP_KEY` 문구를 잘 확인하시여 본인의 Kakao App Key로 변경해주세요. 56 | 57 | ``` 58 | + KAKAO_APP_KEY 59 | + {NATIVE_APP_KEY} 60 | ``` 61 | 62 | AppDelegate.m (++ 된 부분 추가) 63 | 64 | ``` 65 | #import "WithKakaoSDK.h" 66 | 67 | - (BOOL)application:(UIApplication *)app 68 | openURL:(NSURL *)url 69 | options:(NSDictionary *)options 70 | { 71 | ... 72 | 73 | ++ NSString *appKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"KAKAO_APP_KEY"]; 74 | [WithKakaoSDK initSDK: appKey]; 75 | if ([WithKakaoSDK isKakaoTalkLoginUrl:url]) return [WithKakaoSDK handleOpenUrl:url]; 76 | [[FBSDKApplicationDelegate sharedInstance] application:app 77 | openURL:url 78 | options:options]; 79 | 80 | ... 81 | 82 | return NO; 83 | } 84 | ``` 85 | 86 | 다른 수정사항은 npx를 이용해 kakaosdk모듈을 설치 진행 하고 kakaoApiKey 입력을 하시면 자동으로 추가가됩니다. 87 | 88 | 89 | 90 | ### Android 91 | 92 | ## 사용방법 93 | 94 | Project build.gradle 안에 다음과 같이 android sdk repository를 추가해주세요. 95 | 96 | ``` 97 | allprojects { 98 | repositories { 99 | 100 | maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' } 101 | } 102 | } 103 | ``` 104 | 105 | ```js 106 | import KakaoSDK from '@actbase/react-kakaosdk'; 107 | 108 | // 카카오 로그인 시 처리부문 109 | await KakaoSDK.init(NATIVE_APP_KEY); 110 | const tokens = await KakaoSDK.login(); 111 | ``` 112 | 113 | | 변수명 | 설명 | 114 | | ------------------------ | ---------------------------------- | 115 | | access_token | 카카오의 access_token | 116 | | refresh_token | 카카오의 refresh_token | 117 | | expires_in | 카카오의 accessToken 만료 남은 초 | 118 | | refresh_token_expires_in | 카카오의 refreshToken 만료 남은 초 | 119 | | scopes | 사용권한 | 120 | 121 | ```js 122 | import KakaoSDK from '@actbase/react-kakaosdk'; 123 | 124 | // 카카오 로그아웃시 처리 125 | await KakaoSDK.logout(); 126 | 127 | // 카카오 회원정보 가져오기 128 | const profile = await KakaoSDK.getProfile(); 129 | ``` 130 | 131 | # Contacts 132 | 133 | 해당 모듈은 액트베이스(유)에서 개발 및 관리를 진행하고 있습니다.
134 | 프로젝트 문의 혹은 제휴가 필요한 경우 project@actbase.io로 연락주세요. 135 | 136 | # Changes 137 | - 0.9.24 138 | - use_framework! 시 사용가능하도록 변경 139 | - 0.9.20 140 | - 카카오 최신 SDK 반영 141 | - 0.9.19 142 | - 카카오 iOS SDK 장애로 버전 fixed 처리 143 | - 0.9.18 144 | - 액트베이스(유) 오픈소스 기준에 맞춰서 수정 145 | - 0.9.17 146 | - 카카오 로그인 시 keyhash 없는경우 클립보드에 넣어주는 기능 장애 처리 147 | - 0.9.16 148 | - 적폐 제거 (원인제공자는 살림) 149 | - 0.9.14 150 | - 사용하지 않는 명령 제거 151 | - typing 업데이트 152 | - 0.9.12 153 | - 웹버전 scope없을때 장애 처리 154 | - 0.9.11 155 | - 안드로이드 간혈적 에러 처리 156 | - 0.9.10 157 | - 카카오 웹 버전에서도 openChannel 사용가능. 158 | - openChannelChat으로 즉시 채팅 열수있음. 159 | - 0.9.8 160 | - iOS Kakao Login 버그수정 161 | - 0.9.7 162 | - 카카오 채널 기능 버그수정 163 | - 0.9.6 164 | - 카카오 채널 기능 추가 165 | - 0.9.4 166 | - 안드로이드 간혈적 kakaoAccount null 일 경우 처리 167 | - 0.9.3 168 | - 첫 배포 169 | -------------------------------------------------------------------------------- /RNAKakaoSDK.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "RNAKakaoSDK" 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | 10 | s.authors = { "Actbase LLC" => "project@actbase.io" } 11 | s.homepage = package['homepage'] 12 | s.license = package['license'] 13 | 14 | s.platform = :ios, "11.0" 15 | s.framework = 'MobileCoreServices' 16 | s.requires_arc = true 17 | 18 | s.source = { :git => package['repository']['url'] } 19 | s.source_files = "ios/*.{h,m,swift}" 20 | 21 | s.swift_version = '5.1' 22 | 23 | s.ios.deployment_target = '11.0' 24 | s.ios.framework = 'MobileCoreServices' 25 | 26 | s.dependency 'React' 27 | s.dependency 'React-Core' 28 | s.dependency 'KakaoSDKCommon', '2.11.1' 29 | s.dependency 'KakaoSDKAuth', '2.11.1' 30 | s.dependency 'KakaoSDKUser', '2.11.1' 31 | s.dependency 'KakaoSDKTalk', '2.11.1' 32 | s.dependency 'KakaoSDKShare', '2.11.1' 33 | s.dependency 'KakaoSDKTemplate', '2.11.1' 34 | 35 | s.xcconfig = { 36 | "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES" => "YES", 37 | "EMBEDDED_CONTENT_CONTAINS_SWIFT" => "YES", 38 | "BUILD_LIBRARY_FOR_DISTRIBUTION" => "YES" 39 | } 40 | s.pod_target_xcconfig = { 41 | "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES" => "YES", 42 | "EMBEDDED_CONTENT_CONTAINS_SWIFT" => "YES", 43 | "BUILD_LIBRARY_FOR_DISTRIBUTION" => "YES" 44 | } 45 | # s.pod_target_xcconfig = { 'SWIFT_OBJC_BRIDGING_HEADER' => '${PODS_TARGET_SRCROOT}/ios/RNAKakaoSDK-Bridging-Header.h' } 46 | end 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.4.2' 9 | } 10 | } 11 | 12 | 13 | def safeExtGet(prop, fallback) { 14 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 15 | } 16 | 17 | apply plugin: 'com.android.library' 18 | 19 | android { 20 | compileSdkVersion safeExtGet('compileSdkVersion', 29) 21 | 22 | defaultConfig { 23 | minSdkVersion safeExtGet('minSdkVersion', 19) 24 | targetSdkVersion safeExtGet('targetSdkVersion', 29) 25 | versionCode 1 26 | versionName "1.0" 27 | } 28 | 29 | lintOptions { 30 | abortOnError false 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | } 37 | 38 | repositories { 39 | maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' } 40 | } 41 | 42 | dependencies { 43 | implementation 'com.facebook.react:react-native:+' 44 | implementation "com.kakao.sdk:v2-user:2.6.0" 45 | implementation "com.kakao.sdk:v2-talk:2.6.0" 46 | implementation "com.kakao.sdk:v2-story:2.6.0" 47 | implementation "com.kakao.sdk:v2-link:2.6.0" 48 | implementation "com.kakao.sdk:v2-navi:2.6.0" 49 | } 50 | 51 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actbase/react-kakaosdk/a4f56c93db34a8b0e087e4c2107861b4ec277acd/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 18 17:58:04 KST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/io/actbase/kakaosdk/RNAKakaoSDK.java: -------------------------------------------------------------------------------- 1 | package io.actbase.kakaosdk; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | import android.content.pm.PackageInfo; 7 | import android.content.pm.PackageManager; 8 | import android.content.pm.Signature; 9 | import android.net.Uri; 10 | import android.util.Base64; 11 | import android.util.Log; 12 | import android.widget.Toast; 13 | 14 | import com.facebook.react.bridge.Arguments; 15 | import com.facebook.react.bridge.Promise; 16 | import com.facebook.react.bridge.ReactApplicationContext; 17 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 18 | import com.facebook.react.bridge.ReactMethod; 19 | import com.facebook.react.bridge.ReadableArray; 20 | import com.facebook.react.bridge.ReadableMap; 21 | import com.facebook.react.bridge.WritableArray; 22 | import com.facebook.react.bridge.WritableMap; 23 | import com.kakao.sdk.common.KakaoSdk; 24 | import com.kakao.sdk.common.model.AuthError; 25 | import com.kakao.sdk.common.util.KakaoCustomTabsClient; 26 | import com.kakao.sdk.talk.TalkApiClient; 27 | import com.kakao.sdk.user.UserApiClient; 28 | import com.kakao.sdk.user.model.Account; 29 | 30 | import java.security.MessageDigest; 31 | import java.security.NoSuchAlgorithmException; 32 | import java.text.SimpleDateFormat; 33 | import java.util.ArrayList; 34 | import java.util.Date; 35 | import java.util.List; 36 | import java.util.Map; 37 | 38 | public class RNAKakaoSDK extends ReactContextBaseJavaModule { 39 | 40 | private ReactApplicationContext context; 41 | private boolean isInit = false; 42 | 43 | public RNAKakaoSDK(ReactApplicationContext context) { 44 | super(context); 45 | this.context = context; 46 | } 47 | 48 | public String getKeyHash() { 49 | String keyHash = null; 50 | PackageInfo packageInfo = null; 51 | try { 52 | packageInfo = context.getPackageManager() 53 | .getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); 54 | } catch (PackageManager.NameNotFoundException e) { 55 | e.printStackTrace(); 56 | } 57 | 58 | for (Signature signature : packageInfo.signatures) { 59 | try { 60 | MessageDigest md = MessageDigest.getInstance("SHA"); 61 | md.update(signature.toByteArray()); 62 | keyHash = Base64.encodeToString(md.digest(), Base64.DEFAULT); 63 | } catch (NoSuchAlgorithmException e) { 64 | Log.e("KeyHash", "Unable to get MessageDigest. signature=" + signature, e); 65 | } 66 | } 67 | 68 | return keyHash; 69 | } 70 | 71 | @Override 72 | public String getName() { 73 | return "RNAKakaoSDK"; 74 | } 75 | 76 | private String format(Date date) { 77 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 78 | return sdf.format(date); 79 | } 80 | 81 | private boolean toBool(Boolean b) { 82 | return b == null ? false : b; 83 | } 84 | 85 | private void loginWithKakaoAccount(Promise promise) { 86 | // change class into user api at kakao sdk 2.4 87 | UserApiClient.getInstance().loginWithKakaoAccount(context, (token, error) -> { 88 | try { 89 | if (error != null) { 90 | throw error; 91 | } 92 | WritableMap map = Arguments.createMap(); 93 | map.putString("accessToken", token.getAccessToken()); 94 | map.putString("refreshToken", token.getRefreshToken()); 95 | map.putString("accessTokenExpiresAt", format(token.getAccessTokenExpiresAt())); 96 | map.putString("refreshTokenExpiresAt", format(token.getRefreshTokenExpiresAt())); 97 | 98 | WritableArray scopes = Arguments.createArray(); 99 | if (token.getScopes() != null) { 100 | for (String scope : token.getScopes()) { 101 | scopes.pushString(scope); 102 | } 103 | } 104 | map.putArray("scopes", scopes); 105 | 106 | promise.resolve(map); 107 | } catch (Throwable ex) { 108 | if (ex instanceof AuthError) { 109 | AuthError authError = (AuthError) ex; 110 | if (authError.getStatusCode() == 401) { 111 | // invalid android_key_hash or ios_bundle_id or web_site_url. 112 | try { 113 | ClipboardManager clipboard = (ClipboardManager) context 114 | .getSystemService(Context.CLIPBOARD_SERVICE); 115 | ClipData clip = ClipData.newPlainText("Android Key Hash", getKeyHash()); 116 | clipboard.setPrimaryClip(clip); 117 | Toast.makeText(context, "Copy to Keyhash (clipboard)", Toast.LENGTH_SHORT).show(); 118 | } catch (Exception ex2) { 119 | ex2.printStackTrace(); 120 | } 121 | } 122 | promise.reject(String.valueOf(authError.getStatusCode()), authError.getLocalizedMessage(), 123 | ex); 124 | } else { 125 | ex.printStackTrace(); 126 | promise.reject(ex); 127 | } 128 | } 129 | return null; 130 | }); 131 | } 132 | 133 | @ReactMethod 134 | public void init(String appKey) { 135 | KakaoSdk.init(context, appKey); 136 | isInit = true; 137 | } 138 | 139 | @ReactMethod 140 | public void isInitialized(final Promise promise) { 141 | promise.resolve(isInit); 142 | } 143 | 144 | @ReactMethod 145 | public void login(final Promise promise) { 146 | if (!UserApiClient.getInstance().isKakaoTalkLoginAvailable(context)) { 147 | loginWithKakaoAccount(promise); 148 | return; 149 | } 150 | 151 | UserApiClient.getInstance().loginWithKakaoTalk(context.getCurrentActivity(), (token, error) -> { 152 | try { 153 | if (error != null) { 154 | throw error; 155 | } 156 | 157 | WritableMap map = Arguments.createMap(); 158 | map.putString("accessToken", token.getAccessToken()); 159 | map.putString("refreshToken", token.getRefreshToken()); 160 | map.putString("accessTokenExpiresAt", format(token.getAccessTokenExpiresAt())); 161 | map.putString("refreshTokenExpiresAt", format(token.getRefreshTokenExpiresAt())); 162 | 163 | WritableArray scopes = Arguments.createArray(); 164 | 165 | List givenScopes = token.getScopes(); 166 | 167 | if (givenScopes != null) { 168 | for (String scope : givenScopes) { 169 | scopes.pushString(scope); 170 | } 171 | } 172 | 173 | map.putArray("scopes", scopes); 174 | 175 | promise.resolve(map); 176 | } catch (Throwable ex) { 177 | if (ex instanceof AuthError) { 178 | AuthError authError = (AuthError) ex; 179 | if (authError.getStatusCode() == 401) { 180 | // invalid android_key_hash or ios_bundle_id or web_site_url. 181 | try { 182 | ClipboardManager clipboard = (ClipboardManager) context 183 | .getSystemService(Context.CLIPBOARD_SERVICE); 184 | ClipData clip = ClipData.newPlainText("Android Key Hash", getKeyHash()); 185 | clipboard.setPrimaryClip(clip); 186 | Toast.makeText(context, "Copy to Keyhash (clipboard)", Toast.LENGTH_SHORT).show(); 187 | } catch (Exception ex2) { 188 | ex2.printStackTrace(); 189 | } 190 | promise.reject(String.valueOf(authError.getStatusCode()), getKeyHash(), ex); 191 | } else if (authError.getStatusCode() == 302) { 192 | // KakaoTalk is installed but not connected to Kakao account. 193 | loginWithKakaoAccount(promise); 194 | } else { 195 | promise 196 | .reject(String.valueOf(authError.getStatusCode()), authError.getLocalizedMessage(), 197 | ex); 198 | } 199 | } else { 200 | ex.printStackTrace(); 201 | promise.reject(ex); 202 | } 203 | } 204 | return null; 205 | }); 206 | } 207 | 208 | @ReactMethod 209 | public void manualLogin(final Promise promise) { 210 | loginWithKakaoAccount(promise); 211 | } 212 | 213 | @ReactMethod 214 | public void loginWithNewScopes(ReadableArray permissions, final Promise promise) { 215 | List perms = new ArrayList(); 216 | for (int i = 0; i < permissions.size(); i++) { 217 | perms.add(permissions.getString(i)); 218 | } 219 | // change class into user api at kakao sdk 2.4 220 | UserApiClient.getInstance().loginWithNewScopes(context, perms, (token, error) -> { 221 | try { 222 | if (error != null) { 223 | throw new Exception(error.getMessage()); 224 | } 225 | WritableMap map = Arguments.createMap(); 226 | map.putString("accessToken", token.getAccessToken()); 227 | map.putString("refreshToken", token.getRefreshToken()); 228 | map.putString("accessTokenExpiresAt", format(token.getAccessTokenExpiresAt())); 229 | map.putString("refreshTokenExpiresAt", format(token.getRefreshTokenExpiresAt())); 230 | 231 | WritableArray scopes = Arguments.createArray(); 232 | 233 | List givenScopes = token.getScopes(); 234 | 235 | if (givenScopes != null) { 236 | for (String scope : givenScopes) { 237 | scopes.pushString(scope); 238 | } 239 | } 240 | 241 | map.putArray("scopes", scopes); 242 | 243 | promise.resolve(map); 244 | } catch (Throwable ex) { 245 | promise.reject(ex); 246 | } 247 | return null; 248 | }); 249 | } 250 | 251 | @ReactMethod 252 | public void logout(final Promise promise) { 253 | UserApiClient.getInstance().logout((error) -> { 254 | if (error != null) { 255 | promise.reject(error); 256 | } else { 257 | promise.resolve("SUCCESS"); 258 | } 259 | return null; 260 | }); 261 | } 262 | 263 | @ReactMethod 264 | public void unlink(final Promise promise) { 265 | UserApiClient.getInstance().unlink((error) -> { 266 | if (error != null) { 267 | promise.reject(error); 268 | } else { 269 | promise.resolve("SUCCESS"); 270 | } 271 | return null; 272 | }); 273 | } 274 | 275 | @ReactMethod 276 | public void getAccessToken(final Promise promise) { 277 | UserApiClient.getInstance().accessTokenInfo((tokenInfo, error) -> { 278 | try { 279 | if (error != null) { 280 | throw new Exception(error.getMessage()); 281 | } 282 | 283 | WritableMap map = Arguments.createMap(); 284 | map.putDouble("id", tokenInfo.getId()); 285 | map.putDouble("expiresIn", tokenInfo.getExpiresIn()); 286 | promise.resolve(map); 287 | 288 | } catch (Throwable ex) { 289 | promise.reject(ex); 290 | } 291 | return null; 292 | }); 293 | } 294 | 295 | @ReactMethod 296 | public void getProfile(final Promise promise) { 297 | UserApiClient.getInstance().me((user, error) -> { 298 | try { 299 | if (error != null) { 300 | error.printStackTrace(); 301 | throw new Exception(error.getMessage()); 302 | } 303 | 304 | WritableMap map = Arguments.createMap(); 305 | map.putDouble("id", user.getId()); 306 | map.putString("connectedAt", format(user.getConnectedAt())); 307 | 308 | { 309 | WritableMap kakaoAccount = Arguments.createMap(); 310 | Account origin = user.getKakaoAccount(); 311 | if (origin != null) { 312 | if (origin.getEmailNeedsAgreement() != null) { 313 | if (origin.getEmailNeedsAgreement() == Boolean.FALSE) { 314 | kakaoAccount.putString("email", origin.getEmail()); 315 | } 316 | kakaoAccount.putBoolean("emailNeedsAgreement", 317 | toBool(origin.getEmailNeedsAgreement())); 318 | kakaoAccount.putBoolean("isEmailValid", toBool(origin.isEmailValid())); 319 | kakaoAccount.putBoolean("isEmailVerified", toBool(origin.isEmailVerified())); 320 | } 321 | 322 | if (origin.getBirthdayNeedsAgreement() != null) { 323 | if (origin.getBirthdayNeedsAgreement() == Boolean.FALSE) { 324 | kakaoAccount.putString("birthday", origin.getBirthday()); 325 | } 326 | kakaoAccount 327 | .putBoolean("birthdayNeedsAgreement", toBool(origin.getBirthdayNeedsAgreement())); 328 | } 329 | 330 | if (origin.getBirthyearNeedsAgreement() != null) { 331 | if (origin.getBirthyearNeedsAgreement() == Boolean.FALSE) { 332 | kakaoAccount.putString("birthyear", origin.getBirthyear()); 333 | } 334 | kakaoAccount 335 | .putBoolean("birthyearNeedsAgreement", 336 | toBool(origin.getBirthyearNeedsAgreement())); 337 | } 338 | 339 | if (origin.getGenderNeedsAgreement() != null) { 340 | if (origin.getGenderNeedsAgreement() == Boolean.FALSE && origin.getGender() != null) { 341 | kakaoAccount.putString("gender", origin.getGender().toString()); 342 | } 343 | kakaoAccount 344 | .putBoolean("genderNeedsAgreement", toBool(origin.getGenderNeedsAgreement())); 345 | } 346 | 347 | if (origin.getCiNeedsAgreement() != null) { 348 | if (origin.getCiNeedsAgreement() == Boolean.FALSE && origin.getCi() != null) { 349 | kakaoAccount.putString("ci", origin.getCi().toString()); 350 | } 351 | kakaoAccount.putString("ciAuthenticatedAt", format(origin.getCiAuthenticatedAt())); 352 | kakaoAccount.putBoolean("ciNeedsAgreement", toBool(origin.getCiNeedsAgreement())); 353 | } 354 | 355 | if (origin.getLegalBirthDateNeedsAgreement() != null) { 356 | if (origin.getLegalBirthDateNeedsAgreement() == Boolean.FALSE) { 357 | kakaoAccount.putString("legalBirthDate", origin.getLegalBirthDate()); 358 | } 359 | kakaoAccount.putBoolean("legalBirthDateNeedsAgreement", 360 | toBool(origin.getLegalBirthDateNeedsAgreement())); 361 | } 362 | 363 | if (origin.getLegalGenderNeedsAgreement() != null) { 364 | if (origin.getLegalGenderNeedsAgreement() == Boolean.FALSE 365 | && origin.getLegalGender() != null) { 366 | kakaoAccount.putString("legalGender", origin.getLegalGender().toString()); 367 | } 368 | kakaoAccount 369 | .putBoolean("legalGenderNeedsAgreement", 370 | toBool(origin.getLegalGenderNeedsAgreement())); 371 | } 372 | 373 | if (origin.getLegalNameNeedsAgreement() != null) { 374 | if (origin.getLegalNameNeedsAgreement() == Boolean.FALSE) { 375 | kakaoAccount.putString("legalName", origin.getLegalName()); 376 | } 377 | kakaoAccount 378 | .putBoolean("legalNameNeedsAgreement", 379 | toBool(origin.getLegalNameNeedsAgreement())); 380 | } 381 | 382 | if (origin.getAgeRangeNeedsAgreement() != null) { 383 | if (origin.getAgeRangeNeedsAgreement() == Boolean.FALSE 384 | && origin.getAgeRange() != null) { 385 | kakaoAccount.putString("ageRange", origin.getAgeRange().toString()); 386 | } 387 | kakaoAccount 388 | .putBoolean("ageRangeNeedsAgreement", toBool(origin.getAgeRangeNeedsAgreement())); 389 | } 390 | 391 | if (origin.getPhoneNumberNeedsAgreement() != null) { 392 | if (origin.getPhoneNumberNeedsAgreement() == Boolean.FALSE) { 393 | kakaoAccount.putString("phoneNumber", origin.getPhoneNumber()); 394 | } 395 | kakaoAccount 396 | .putBoolean("phoneNumberNeedsAgreement", 397 | toBool(origin.getPhoneNumberNeedsAgreement())); 398 | } 399 | 400 | if (origin.getProfileNeedsAgreement() != null) { 401 | if (origin.getProfileNeedsAgreement() == Boolean.FALSE) { 402 | WritableMap profile = Arguments.createMap(); 403 | profile.putString("nickname", origin.getProfile().getNickname()); 404 | profile.putString("profileImageUrl", origin.getProfile().getProfileImageUrl()); 405 | profile.putString("thumbnailImageUrl", origin.getProfile().getThumbnailImageUrl()); 406 | kakaoAccount.putMap("profile", profile); 407 | } 408 | kakaoAccount 409 | .putBoolean("profileNeedsAgreement", toBool(origin.getProfileNeedsAgreement())); 410 | } 411 | } 412 | map.putMap("kakaoAccount", kakaoAccount); 413 | } 414 | 415 | { 416 | WritableMap properties = Arguments.createMap(); 417 | Map origin = user.getProperties(); 418 | if (origin != null) { 419 | for (String key : origin.keySet()) { 420 | if (origin.get(key) != null) { 421 | properties.putString(key, origin.get(key)); 422 | } 423 | } 424 | } 425 | map.putMap("properties", properties); 426 | } 427 | promise.resolve(map); 428 | 429 | } catch (Throwable ex) { 430 | promise.reject(ex); 431 | } 432 | return null; 433 | }); 434 | } 435 | 436 | @ReactMethod 437 | public void openChannel(String url, final Promise promise) { 438 | Uri talkUrl = TalkApiClient.getInstance().addChannelUrl(url); 439 | KakaoCustomTabsClient.INSTANCE.openWithDefault( 440 | getReactApplicationContext().getCurrentActivity(), talkUrl); 441 | promise.resolve(true); 442 | } 443 | 444 | @ReactMethod 445 | public void openChannelChat(String url, final Promise promise) { 446 | Uri talkUrl = TalkApiClient.getInstance().channelChatUrl(url); 447 | KakaoCustomTabsClient.INSTANCE.openWithDefault( 448 | getReactApplicationContext().getCurrentActivity(), talkUrl); 449 | promise.resolve(true); 450 | } 451 | 452 | public String getKeyHash(final Context context) { 453 | PackageInfo packageInfo = null; 454 | try { 455 | packageInfo = context.getPackageManager() 456 | .getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); 457 | } catch (PackageManager.NameNotFoundException e) { 458 | e.printStackTrace(); 459 | } 460 | 461 | if (packageInfo == null) { 462 | return null; 463 | } 464 | for (Signature signature : packageInfo.signatures) { 465 | try { 466 | MessageDigest md = MessageDigest.getInstance("SHA"); 467 | md.update(signature.toByteArray()); 468 | return Base64.encodeToString(md.digest(), Base64.NO_WRAP); 469 | } catch (NoSuchAlgorithmException e) { 470 | e.printStackTrace(); 471 | } 472 | } 473 | return null; 474 | } 475 | 476 | } 477 | 478 | -------------------------------------------------------------------------------- /android/src/main/java/io/actbase/kakaosdk/RNAKakaoSDKPackage.java: -------------------------------------------------------------------------------- 1 | package io.actbase.kakaosdk; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | 13 | public class RNAKakaoSDKPackage implements ReactPackage { 14 | 15 | @Override 16 | public List createNativeModules(ReactApplicationContext reactContext) { 17 | return Arrays.asList( 18 | new RNAKakaoSDK(reactContext) 19 | ); 20 | } 21 | 22 | // Deprecated from RN 0.47 23 | public List> createJSModules() { 24 | return Collections.emptyList(); 25 | } 26 | 27 | @Override 28 | public List createViewManagers(ReactApplicationContext reactContext) { 29 | return Collections.emptyList(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /assets/xcode_0501.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actbase/react-kakaosdk/a4f56c93db34a8b0e087e4c2107861b4ec277acd/assets/xcode_0501.png -------------------------------------------------------------------------------- /ios/RNAKakaoSDK-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARNKakaoLogin-Bridging-Header.h 3 | // ARNKakaoLogin 4 | // 5 | // Created by Suhan Moon on 2020/08/27. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | 12 | -------------------------------------------------------------------------------- /ios/RNAKakaoSDK.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RNAKakaoSDK.swift 3 | // RNAKakaoSDK 4 | // 5 | // Created by Suhan Moon on 2020/08/27. 6 | // 7 | 8 | import Foundation 9 | import KakaoSDKCommon 10 | import KakaoSDKAuth 11 | import KakaoSDKUser 12 | import SafariServices 13 | import KakaoSDKTalk 14 | 15 | @objc(RNAKakaoSDK) 16 | public class RNAKakaoSDK: NSObject { 17 | 18 | fileprivate var inited = false; 19 | 20 | @objc 21 | static func requiresMainQueueSetup() -> Bool { 22 | return true 23 | } 24 | 25 | @objc(isKakaoTalkLoginUrl:) 26 | public static func isKakaoTalkLoginUrl(url:URL) -> Bool { 27 | return AuthApi.isKakaoTalkLoginUrl(url) 28 | } 29 | 30 | @objc(handleOpenUrl:) 31 | public static func handleOpenUrl(url:URL) -> Bool { 32 | return AuthController.handleOpenUrl(url: url) 33 | } 34 | 35 | func objectToDic(_ value: T) throws -> Any where T: Encodable { 36 | let json = try JSONEncoder().encode(value) 37 | let dict = try JSONSerialization.jsonObject(with: json, options: .allowFragments); 38 | return dict; 39 | } 40 | 41 | @objc(init:) 42 | func sdkinit(_ appKey: String) -> Void { 43 | KakaoSDK.initSDK(appKey: appKey) 44 | inited = true 45 | } 46 | 47 | @objc(isInitialized:rejecter:) 48 | func isInitialized(_ resolve: @escaping RCTPromiseResolveBlock, 49 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 50 | resolve(inited); 51 | } 52 | 53 | @objc(login:rejecter:) 54 | func login(_ resolve: @escaping RCTPromiseResolveBlock, 55 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 56 | 57 | DispatchQueue.main.async { 58 | let dateFormatter = DateFormatter() 59 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"; 60 | 61 | if (UserApi.isKakaoTalkLoginAvailable()) { 62 | UserApi.shared.loginWithKakaoTalk {(oauthToken, error) in 63 | do { 64 | if let error = error { 65 | throw error; 66 | } 67 | resolve([ 68 | "accessToken": oauthToken!.accessToken, 69 | "refreshToken": oauthToken!.refreshToken, 70 | "accessTokenExpiresAt": dateFormatter.string(from: oauthToken!.expiredAt), 71 | "refreshTokenExpiresAt": dateFormatter.string(from: oauthToken!.refreshTokenExpiredAt), 72 | "scopes": oauthToken?.scopes, 73 | ]) 74 | } catch let e { 75 | print(e); 76 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 77 | } 78 | } 79 | } 80 | else { 81 | UserApi.shared.loginWithKakaoAccount {(oauthToken, error) in 82 | do { 83 | if let error = error { 84 | throw error; 85 | } 86 | 87 | resolve([ 88 | "accessToken": oauthToken!.accessToken, 89 | "refreshToken": oauthToken!.refreshToken, 90 | "accessTokenExpiresAt": dateFormatter.string(from: oauthToken!.expiredAt), 91 | "refreshTokenExpiresAt": dateFormatter.string(from: oauthToken!.refreshTokenExpiredAt), 92 | "scopes": oauthToken?.scopes, 93 | ]) 94 | } catch let e { 95 | print(e); 96 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | @objc(manualLogin:rejecter:) 104 | func manualLogin(_ resolve: @escaping RCTPromiseResolveBlock, 105 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 106 | 107 | DispatchQueue.main.async { 108 | let dateFormatter = DateFormatter() 109 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"; 110 | UserApi.shared.loginWithKakaoAccount {(oauthToken, error) in 111 | do { 112 | if let error = error { 113 | throw error; 114 | } 115 | 116 | resolve([ 117 | "accessToken": oauthToken!.accessToken, 118 | "refreshToken": oauthToken!.refreshToken, 119 | "accessTokenExpiresAt": dateFormatter.string(from: oauthToken!.expiredAt), 120 | "refreshTokenExpiresAt": dateFormatter.string(from: oauthToken!.refreshTokenExpiredAt), 121 | "scopes": oauthToken?.scopes, 122 | ]) 123 | } catch let e { 124 | print(e); 125 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 126 | } 127 | } 128 | } 129 | } 130 | 131 | @objc(loginWithNewScopes:resolver:rejecter:) 132 | func loginWithNewScopes(_ scopedata: NSArray, 133 | resolver resolve: @escaping RCTPromiseResolveBlock, 134 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 135 | DispatchQueue.main.async { 136 | let dateFormatter = DateFormatter() 137 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"; 138 | 139 | let scopes = scopedata as? [String] 140 | UserApi.shared.loginWithKakaoAccount(scopes: scopes!) { (oauthToken, error) in 141 | if let error = error { 142 | reject("RCTKakaoSDK", error.asAFError?.errorDescription, nil) 143 | return 144 | } 145 | else { 146 | do { 147 | if let error = error { 148 | throw error; 149 | } 150 | 151 | resolve([ 152 | "accessToken": oauthToken!.accessToken, 153 | "refreshToken": oauthToken!.refreshToken, 154 | "accessTokenExpiresAt": dateFormatter.string(from: oauthToken!.expiredAt), 155 | "refreshTokenExpiresAt": dateFormatter.string(from: oauthToken!.refreshTokenExpiredAt), 156 | "scopes": oauthToken?.scopes!, 157 | ]) 158 | 159 | } catch let e { 160 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | @objc(logout:rejecter:) 168 | func logout(_ resolve: @escaping RCTPromiseResolveBlock, 169 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 170 | 171 | DispatchQueue.main.async { 172 | UserApi.shared.logout {(error) in 173 | do { 174 | if let error = error { 175 | throw error; 176 | } 177 | resolve("SUCCESS") 178 | } catch let e { 179 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 180 | } 181 | } 182 | } 183 | } 184 | 185 | @objc(unlink:rejecter:) 186 | func unlink(_ resolve: @escaping RCTPromiseResolveBlock, 187 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 188 | 189 | DispatchQueue.main.async { 190 | UserApi.shared.unlink {(error) in 191 | do { 192 | if let error = error { 193 | throw error; 194 | } 195 | resolve("SUCCESS") 196 | } catch let e { 197 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 198 | } 199 | } 200 | } 201 | } 202 | 203 | @objc(getAccessToken:rejecter:) 204 | func getAccessToken(_ resolve: @escaping RCTPromiseResolveBlock, 205 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 206 | 207 | DispatchQueue.main.async { 208 | UserApi.shared.accessTokenInfo {(accessTokenInfo, error) in 209 | do { 210 | if let error = error { 211 | throw error; 212 | } 213 | resolve([ 214 | "id": accessTokenInfo?.id, 215 | "expiresIn": accessTokenInfo?.expiresIn 216 | ]) 217 | } catch let e { 218 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 219 | } 220 | } 221 | } 222 | } 223 | 224 | @objc(getProfile:rejecter:) 225 | func getProfile(_ resolve: @escaping RCTPromiseResolveBlock, 226 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 227 | 228 | DispatchQueue.main.async { 229 | let dateFormatter = DateFormatter() 230 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"; 231 | UserApi.shared.me() {(user, error) in 232 | do { 233 | if let error = error { 234 | throw error; 235 | } 236 | 237 | var map: [String: Any] = [ 238 | "id": user!.id as Any, 239 | "connectedAt": dateFormatter.string(from: user!.connectedAt!) 240 | ]; 241 | 242 | var kakaoAccount: [String: Any] = [:] 243 | let origin: Account = user!.kakaoAccount! 244 | 245 | if (origin.emailNeedsAgreement != nil) { 246 | if (origin.emailNeedsAgreement == false) { 247 | kakaoAccount.updateValue(origin.email ?? "", forKey: "email") 248 | } 249 | kakaoAccount.updateValue(origin.emailNeedsAgreement ?? false, forKey: "emailNeedsAgreement") 250 | kakaoAccount.updateValue(origin.isEmailValid ?? false, forKey: "isEmailValid") 251 | kakaoAccount.updateValue(origin.isEmailVerified ?? false, forKey: "isEmailVerified") 252 | } 253 | 254 | if (origin.birthdayNeedsAgreement != nil) { 255 | if (origin.birthdayNeedsAgreement == false) { 256 | kakaoAccount.updateValue(origin.birthday ?? "", forKey: "birthday") 257 | } 258 | kakaoAccount.updateValue(origin.birthdayNeedsAgreement ?? false, forKey: "birthdayNeedsAgreement") 259 | } 260 | 261 | if (origin.birthyearNeedsAgreement != nil) { 262 | if (origin.birthyearNeedsAgreement == false) { 263 | kakaoAccount.updateValue(origin.birthyear, forKey: "birthyear") 264 | } 265 | kakaoAccount.updateValue(origin.birthyearNeedsAgreement, forKey: "birthyearNeedsAgreement") 266 | } 267 | 268 | if (origin.genderNeedsAgreement != nil) { 269 | if (origin.genderNeedsAgreement == false) { 270 | kakaoAccount.updateValue(origin.gender?.rawValue, forKey: "gender") 271 | } 272 | kakaoAccount.updateValue(origin.genderNeedsAgreement, forKey: "genderNeedsAgreement") 273 | } 274 | 275 | if (origin.ciNeedsAgreement != nil) { 276 | if (origin.ciNeedsAgreement == false) { 277 | kakaoAccount.updateValue(origin.ci, forKey: "ci") 278 | } 279 | kakaoAccount.updateValue(dateFormatter.string(from: origin.ciAuthenticatedAt!), forKey: "ciAuthenticatedAt") 280 | kakaoAccount.updateValue(origin.ciNeedsAgreement, forKey: "ciNeedsAgreement") 281 | } 282 | 283 | if (origin.legalBirthDateNeedsAgreement != nil) { 284 | if (origin.legalBirthDateNeedsAgreement == false) { 285 | kakaoAccount.updateValue(origin.legalBirthDate, forKey: "legalBirthDate") 286 | } 287 | kakaoAccount.updateValue(origin.legalBirthDateNeedsAgreement, forKey: "legalBirthDateNeedsAgreement") 288 | } 289 | 290 | if (origin.legalGenderNeedsAgreement != nil) { 291 | if (origin.legalGenderNeedsAgreement == false) { 292 | kakaoAccount.updateValue(origin.legalGender?.rawValue, forKey: "legalGender") 293 | } 294 | kakaoAccount.updateValue(origin.legalGenderNeedsAgreement, forKey: "legalGenderNeedsAgreement") 295 | } 296 | 297 | if (origin.legalNameNeedsAgreement != nil) { 298 | if (origin.legalNameNeedsAgreement == false) { 299 | kakaoAccount.updateValue(origin.legalName, forKey: "legalName") 300 | } 301 | kakaoAccount.updateValue(origin.legalNameNeedsAgreement, forKey: "legalNameNeedsAgreement") 302 | } 303 | 304 | if (origin.ageRangeNeedsAgreement != nil) { 305 | if (origin.ageRangeNeedsAgreement == false) { 306 | kakaoAccount.updateValue(origin.ageRange?.rawValue, forKey: "ageRange") 307 | } 308 | kakaoAccount.updateValue(origin.ageRangeNeedsAgreement, forKey: "ageRangeNeedsAgreement") 309 | } 310 | 311 | if (origin.phoneNumberNeedsAgreement != nil) { 312 | if (origin.phoneNumberNeedsAgreement == false) { 313 | kakaoAccount.updateValue(origin.phoneNumber, forKey: "phoneNumber") 314 | } 315 | kakaoAccount.updateValue(origin.phoneNumberNeedsAgreement, forKey: "phoneNumberNeedsAgreement") 316 | } 317 | 318 | if (origin.profileNeedsAgreement != nil) { 319 | if (origin.profileNeedsAgreement == false) { 320 | kakaoAccount.updateValue([ 321 | "nickname": origin.profile?.nickname, 322 | "profileImageUrl": origin.profile?.profileImageUrl, 323 | "thumbnailImageUrl": origin.profile?.thumbnailImageUrl, 324 | ], forKey: "profile") 325 | } 326 | kakaoAccount.updateValue(origin.profileNeedsAgreement, forKey: "profileNeedsAgreement") 327 | } 328 | map.updateValue(kakaoAccount, forKey: "kakaoAccount") 329 | map.updateValue(user?.properties, forKey: "properties"); 330 | resolve(map) 331 | } catch let e { 332 | reject("actbase_kakao_sdk", e.localizedDescription, nil) 333 | } 334 | } 335 | } 336 | } 337 | 338 | @objc(openChannel:resolver:rejecter:) 339 | func openChannel(_ channelId: String, 340 | resolver resolve: @escaping RCTPromiseResolveBlock, 341 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 342 | var safariViewController : SFSafariViewController = SFSafariViewController(url: TalkApi.shared.makeUrlForAddChannel(channelPublicId:channelId)!) 343 | guard (safariViewController != nil) else { return } 344 | 345 | DispatchQueue.main.async { 346 | safariViewController.modalTransitionStyle = .crossDissolve 347 | safariViewController.modalPresentationStyle = .overCurrentContext 348 | UIApplication.shared.keyWindow?.rootViewController?.present(safariViewController, animated: true) { 349 | print("카카오톡 채널 추가 연결 페이지 실행 성공") 350 | } 351 | resolve(true) 352 | } 353 | } 354 | 355 | @objc(openChannelChat:resolver:rejecter:) 356 | func openChannelChat(_ channelId: String, 357 | resolver resolve: @escaping RCTPromiseResolveBlock, 358 | rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { 359 | var safariViewController : SFSafariViewController = SFSafariViewController(url: TalkApi.shared.makeUrlForChannelChat(channelPublicId:channelId)!) 360 | guard (safariViewController != nil) else { return } 361 | 362 | DispatchQueue.main.async { 363 | safariViewController.modalTransitionStyle = .crossDissolve 364 | safariViewController.modalPresentationStyle = .overCurrentContext 365 | UIApplication.shared.keyWindow?.rootViewController?.present(safariViewController, animated: true) { 366 | print("Kakao Talk Channel chat 연결 페이지 실행 성공") 367 | } 368 | resolve(true) 369 | } 370 | } 371 | 372 | } 373 | -------------------------------------------------------------------------------- /ios/RNAKakaoSDK.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXCopyFilesBuildPhase section */ 10 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 11 | isa = PBXCopyFilesBuildPhase; 12 | buildActionMask = 2147483647; 13 | dstPath = "include/$(PRODUCT_NAME)"; 14 | dstSubfolderSpec = 16; 15 | files = ( 16 | ); 17 | runOnlyForDeploymentPostprocessing = 0; 18 | }; 19 | /* End PBXCopyFilesBuildPhase section */ 20 | 21 | /* Begin PBXFrameworksBuildPhase section */ 22 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 23 | isa = PBXFrameworksBuildPhase; 24 | buildActionMask = 2147483647; 25 | files = ( 26 | BA67EA73238522010094C010 /* KakaoOpenSDK.framework in Frameworks */, 27 | ); 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXFrameworksBuildPhase section */ 31 | 32 | 33 | /* Begin PBXNativeTarget section */ 34 | 58B511DA1A9E6C8500147676 /* RNCKakaoSDK */ = { 35 | isa = PBXNativeTarget; 36 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCKakaoSDK" */; 37 | buildPhases = ( 38 | 58B511D71A9E6C8500147676 /* Sources */, 39 | 58B511D81A9E6C8500147676 /* Frameworks */, 40 | 58B511D91A9E6C8500147676 /* CopyFiles */, 41 | ); 42 | buildRules = ( 43 | ); 44 | dependencies = ( 45 | ); 46 | name = RNCKakaoSDK; 47 | productName = RCTDataManager; 48 | productReference = 134814201AA4EA6300B7C361 /* libRNCcsKakaosdk.a */; 49 | productType = "com.apple.product-type.library.static"; 50 | }; 51 | /* End PBXNativeTarget section */ 52 | 53 | /* Begin XCBuildConfiguration section */ 54 | 58B511ED1A9E6C8500147676 /* Debug */ = { 55 | isa = XCBuildConfiguration; 56 | buildSettings = { 57 | ALWAYS_SEARCH_USER_PATHS = NO; 58 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 59 | CLANG_CXX_LIBRARY = "libc++"; 60 | CLANG_ENABLE_MODULES = YES; 61 | CLANG_ENABLE_OBJC_ARC = YES; 62 | CLANG_WARN_BOOL_CONVERSION = YES; 63 | CLANG_WARN_CONSTANT_CONVERSION = YES; 64 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 65 | CLANG_WARN_EMPTY_BODY = YES; 66 | CLANG_WARN_ENUM_CONVERSION = YES; 67 | CLANG_WARN_INFINITE_RECURSION = YES; 68 | CLANG_WARN_INT_CONVERSION = YES; 69 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 70 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 71 | CLANG_WARN_UNREACHABLE_CODE = YES; 72 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 73 | COPY_PHASE_STRIP = NO; 74 | ENABLE_STRICT_OBJC_MSGSEND = YES; 75 | ENABLE_TESTABILITY = YES; 76 | GCC_C_LANGUAGE_STANDARD = gnu99; 77 | GCC_DYNAMIC_NO_PIC = NO; 78 | GCC_NO_COMMON_BLOCKS = YES; 79 | GCC_OPTIMIZATION_LEVEL = 0; 80 | GCC_PREPROCESSOR_DEFINITIONS = ( 81 | "DEBUG=1", 82 | "$(inherited)", 83 | ); 84 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 85 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 86 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 87 | GCC_WARN_UNDECLARED_SELECTOR = YES; 88 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 89 | GCC_WARN_UNUSED_FUNCTION = YES; 90 | GCC_WARN_UNUSED_VARIABLE = YES; 91 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 92 | MTL_ENABLE_DEBUG_INFO = YES; 93 | ONLY_ACTIVE_ARCH = YES; 94 | OTHER_LDFLAGS = "-all_load"; 95 | SDKROOT = iphoneos; 96 | }; 97 | name = Debug; 98 | }; 99 | 58B511EE1A9E6C8500147676 /* Release */ = { 100 | isa = XCBuildConfiguration; 101 | buildSettings = { 102 | ALWAYS_SEARCH_USER_PATHS = NO; 103 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 104 | CLANG_CXX_LIBRARY = "libc++"; 105 | CLANG_ENABLE_MODULES = YES; 106 | CLANG_ENABLE_OBJC_ARC = YES; 107 | CLANG_WARN_BOOL_CONVERSION = YES; 108 | CLANG_WARN_CONSTANT_CONVERSION = YES; 109 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 110 | CLANG_WARN_EMPTY_BODY = YES; 111 | CLANG_WARN_ENUM_CONVERSION = YES; 112 | CLANG_WARN_INFINITE_RECURSION = YES; 113 | CLANG_WARN_INT_CONVERSION = YES; 114 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 115 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 116 | CLANG_WARN_UNREACHABLE_CODE = YES; 117 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 118 | COPY_PHASE_STRIP = YES; 119 | ENABLE_NS_ASSERTIONS = NO; 120 | ENABLE_STRICT_OBJC_MSGSEND = YES; 121 | GCC_C_LANGUAGE_STANDARD = gnu99; 122 | GCC_NO_COMMON_BLOCKS = YES; 123 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 124 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 125 | GCC_WARN_UNDECLARED_SELECTOR = YES; 126 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 127 | GCC_WARN_UNUSED_FUNCTION = YES; 128 | GCC_WARN_UNUSED_VARIABLE = YES; 129 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 130 | MTL_ENABLE_DEBUG_INFO = NO; 131 | OTHER_LDFLAGS = "-all_load"; 132 | SDKROOT = iphoneos; 133 | VALIDATE_PRODUCT = YES; 134 | }; 135 | name = Release; 136 | }; 137 | 58B511F01A9E6C8500147676 /* Debug */ = { 138 | isa = XCBuildConfiguration; 139 | buildSettings = { 140 | FRAMEWORK_SEARCH_PATHS = ( 141 | "$(inherited)", 142 | "$(PROJECT_DIR)", 143 | ); 144 | HEADER_SEARCH_PATHS = ( 145 | "$(inherited)", 146 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 147 | "$(SRCROOT)/../../../React/**", 148 | "$(SRCROOT)/../../react-native/React/**", 149 | "$(SRCROOT)/../../../ios/Pods/Headers/Public/**", 150 | ); 151 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 152 | OTHER_LDFLAGS = "-ObjC"; 153 | PRODUCT_NAME = RNCKakaoSDK; 154 | SKIP_INSTALL = YES; 155 | }; 156 | name = Debug; 157 | }; 158 | 58B511F11A9E6C8500147676 /* Release */ = { 159 | isa = XCBuildConfiguration; 160 | buildSettings = { 161 | FRAMEWORK_SEARCH_PATHS = ( 162 | "$(inherited)", 163 | "$(PROJECT_DIR)", 164 | ); 165 | HEADER_SEARCH_PATHS = ( 166 | "$(inherited)", 167 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 168 | "$(SRCROOT)/../../../React/**", 169 | "$(SRCROOT)/../../react-native/React/**", 170 | "$(SRCROOT)/../../../ios/Pods/Headers/Public/**", 171 | ); 172 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 173 | OTHER_LDFLAGS = "-ObjC"; 174 | PRODUCT_NAME = RNCKakaoSDK; 175 | SKIP_INSTALL = YES; 176 | }; 177 | name = Release; 178 | }; 179 | /* End XCBuildConfiguration section */ 180 | 181 | /* Begin XCConfigurationList section */ 182 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "ARNKakaoLogin" */ = { 183 | isa = XCConfigurationList; 184 | buildConfigurations = ( 185 | 58B511ED1A9E6C8500147676 /* Debug */, 186 | 58B511EE1A9E6C8500147676 /* Release */, 187 | ); 188 | defaultConfigurationIsVisible = 0; 189 | defaultConfigurationName = Release; 190 | }; 191 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCKakaoSDK" */ = { 192 | isa = XCConfigurationList; 193 | buildConfigurations = ( 194 | 58B511F01A9E6C8500147676 /* Debug */, 195 | 58B511F11A9E6C8500147676 /* Release */, 196 | ); 197 | defaultConfigurationIsVisible = 0; 198 | defaultConfigurationName = Release; 199 | }; 200 | /* End XCConfigurationList section */ 201 | }; 202 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 203 | } 204 | -------------------------------------------------------------------------------- /ios/RNAKakaoSDKModule.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RNAKakaoSDKModule: NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/RNAKakaoSDKModule.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "RNAKakaoSDKModule.h" 3 | 4 | @interface RCT_EXTERN_MODULE(RNAKakaoSDK, NSObject) 5 | RCT_EXTERN_METHOD(initSDK:(NSString *)appKey); 6 | RCT_EXTERN_METHOD(init:(NSString *)appKey); 7 | RCT_EXTERN_METHOD(isInitialized:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject); 8 | RCT_EXTERN_METHOD(login:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject); 9 | RCT_EXTERN_METHOD(manualLogin:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject); 10 | RCT_EXTERN_METHOD(logout:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject); 11 | RCT_EXTERN_METHOD(unlink:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject); 12 | RCT_EXTERN_METHOD(getAccessToken:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject); 13 | RCT_EXTERN_METHOD(getProfile:(RCTPromiseResolveBlock *)resolve rejecter:(RCTPromiseRejectBlock *)reject); 14 | RCT_EXTERN_METHOD(loginWithNewScopes:(NSArray *)scopes 15 | resolver:(RCTPromiseResolveBlock *)resolve 16 | rejecter:(RCTPromiseRejectBlock *)reject); 17 | RCT_EXTERN_METHOD(openChannel:(NSString *)channelId 18 | resolver:(RCTPromiseResolveBlock *)resolve 19 | rejecter:(RCTPromiseRejectBlock *)reject); 20 | RCT_EXTERN_METHOD(openChannelChat:(NSString *)channelId 21 | resolver:(RCTPromiseResolveBlock *)resolve 22 | rejecter:(RCTPromiseRejectBlock *)reject); 23 | @end 24 | -------------------------------------------------------------------------------- /ios/WithKakaoSDK.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface WithKakaoSDK : NSObject 6 | 7 | + (BOOL)isKakaoTalkLoginUrl:(NSURL *)url; 8 | + (BOOL)handleOpenUrl:(NSURL *)url; 9 | 10 | @end 11 | 12 | NS_ASSUME_NONNULL_END 13 | 14 | -------------------------------------------------------------------------------- /ios/WithKakaoSDK.m: -------------------------------------------------------------------------------- 1 | #import "WithKakaoSDK.h" 2 | #import 3 | 4 | @implementation WithKakaoSDK 5 | 6 | + (BOOL)isKakaoTalkLoginUrl:(NSURL *)url { 7 | return [RNAKakaoSDK isKakaoTalkLoginUrl:url]; 8 | } 9 | 10 | + (BOOL)handleOpenUrl:(NSURL *)url { 11 | return [RNAKakaoSDK handleOpenUrl:url]; 12 | 13 | } 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@actbase/react-kakaosdk", 3 | "bin": { 4 | "install": "./bin/install" 5 | }, 6 | "version": "0.9.26", 7 | "description": "KakaoSDK for React, ReactNative ", 8 | "main": "lib/index.js", 9 | "module": "lib/index.js", 10 | "typings": "lib/index.d.ts", 11 | "scripts": { 12 | "build:src": "rm -rf ./lib && tsc -p ./tsconfig.json && babel lib --out-dir lib", 13 | "build:bin": "rm -rf ./bin && babel src_bin --out-dir bin --copy-files", 14 | "build": "npm run build:src && npm run build:bin", 15 | "prepublish": "npm run build", 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/actbase/react-kakaosdk.git" 21 | }, 22 | "keywords": [ 23 | "react", 24 | "react-native", 25 | "reactnative", 26 | "kakaosdk", 27 | "react kakao", 28 | "react native kakao", 29 | "react-native kakao", 30 | "react kakao login", 31 | "react native kakao login", 32 | "react-native kakao login" 33 | ], 34 | "author": { 35 | "name": "Actbase LLC", 36 | "email": "project@actbase.io", 37 | "url": "https://da.actbase.io" 38 | }, 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/actbase/react-kakaosdk/issues" 42 | }, 43 | "homepage": "https://github.com/actbase/react-kakaosdk#readme", 44 | "devDependencies": { 45 | "@babel/cli": "^7.6.2", 46 | "@babel/core": "^7.6.2", 47 | "@babel/node": "^7.7.7", 48 | "@babel/plugin-proposal-class-properties": "^7.7.4", 49 | "@babel/plugin-proposal-optional-chaining": "^7.9.0", 50 | "@babel/plugin-transform-modules-commonjs": "^7.7.5", 51 | "@babel/plugin-transform-react-jsx": "^7.8.3", 52 | "@babel/preset-env": "^7.7.7", 53 | "@babel/preset-react": "^7.7.4", 54 | "@types/lodash": "^4.14.149", 55 | "@types/react": "^16.9.31", 56 | "@types/react-dom": "^16.9.5", 57 | "@types/react-native": "0.61.12", 58 | "@typescript-eslint/eslint-plugin": "^4.29.3", 59 | "@typescript-eslint/parser": "^4.29.3", 60 | "babel-eslint": "^10.0.3", 61 | "babel-plugin-inline-import-data-uri": "^1.0.1", 62 | "commander": "^4.1.1", 63 | "esifycss": "^1.4.12", 64 | "eslint": "^7.32.0", 65 | "eslint-config-prettier": "^6.10.0", 66 | "eslint-plugin-babel": "^5.3.1", 67 | "eslint-plugin-import": "^2.20.1", 68 | "eslint-plugin-json": "^2.1.0", 69 | "eslint-plugin-jsx-a11y": "^6.2.3", 70 | "eslint-plugin-react": "^7.18.3", 71 | "eslint-plugin-react-hooks": "^4.2.0", 72 | "eslint-plugin-react-native": "^3.8.1", 73 | "inquirer": "^7.3.3", 74 | "jest": "^25.1.0", 75 | "prettier": "^1.19.1", 76 | "react": "*", 77 | "react-native": "*", 78 | "tslib": "^2.3.1", 79 | "typescript": "^4.4.2" 80 | }, 81 | "dependencies": { 82 | "plist": "^3.0.2" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/app.native.ts: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native'; 2 | import { KakaoSDK, ProfileType } from './types'; 3 | 4 | const { RNAKakaoSDK } = NativeModules; 5 | 6 | const dateToSeconds = (str: string): number => { 7 | const v = new Date(str?.replace(' ', 'T')).getTime(); 8 | const n = new Date().getTime(); 9 | return Math.floor((v - n) / 1000); 10 | }; 11 | 12 | const valueToSnakeCase = (data: { [key: string]: any }) => { 13 | const args: { [key: string]: any } = {}; 14 | for (const key of Object.keys(data)) { 15 | const nkey: string = key.replace(/(?:^|\.?)([A-Z])/g, (_x, y) => '_' + y.toLowerCase()).replace(/^_/, ''); 16 | if (data[key] && typeof data[key] === 'object' && !data[key]?.push && Object.keys(data[key])?.length > 0) { 17 | args[nkey] = valueToSnakeCase(data[key]); 18 | } else { 19 | args[nkey] = data[key]; 20 | } 21 | } 22 | return args; 23 | }; 24 | 25 | export const init = async (appKey: string) => { 26 | RNAKakaoSDK.init(appKey); 27 | }; 28 | 29 | export const isInitialized = async () => { 30 | return await RNAKakaoSDK.isInitialized(); 31 | }; 32 | 33 | export const login = async () => { 34 | const result = await RNAKakaoSDK.login(); 35 | return { 36 | access_token: result?.accessToken, 37 | refresh_token: result?.refreshToken, 38 | scopes: result?.scopes, 39 | expires_in: dateToSeconds(result?.accessTokenExpiresAt), 40 | refresh_token_expires_in: dateToSeconds(result?.refreshTokenExpiresAt), 41 | }; 42 | }; 43 | 44 | export const manualLogin = async () => { 45 | const result = await RNAKakaoSDK.manualLogin(); 46 | return { 47 | access_token: result?.accessToken, 48 | refresh_token: result?.refreshToken, 49 | scopes: result?.scopes, 50 | expires_in: dateToSeconds(result?.accessTokenExpiresAt), 51 | refresh_token_expires_in: dateToSeconds(result?.refreshTokenExpiresAt), 52 | }; 53 | }; 54 | 55 | export const loginWithNewScopes = async (scopes: string[]) => { 56 | const result = await RNAKakaoSDK.loginWithNewScopes(scopes); 57 | return { 58 | access_token: result?.accessToken, 59 | refresh_token: result?.refreshToken, 60 | scopes: result?.scopes, 61 | expires_in: dateToSeconds(result?.accessTokenExpiresAt), 62 | refresh_token_expires_in: dateToSeconds(result?.refreshTokenExpiresAt), 63 | }; 64 | }; 65 | 66 | export const logout = async () => { 67 | await RNAKakaoSDK.logout(); 68 | }; 69 | 70 | export const unlink = async () => { 71 | await RNAKakaoSDK.unlink(); 72 | }; 73 | 74 | export const getAccessToken = async () => { 75 | const result = await RNAKakaoSDK.getAccessToken(); 76 | return { 77 | id: result.id, 78 | expires_in: dateToSeconds(result?.expiresIn), 79 | }; 80 | }; 81 | 82 | export const getProfile: () => Promise = async () => { 83 | const result = await RNAKakaoSDK.getProfile(); 84 | return valueToSnakeCase(result) as ProfileType; 85 | }; 86 | 87 | export const openChannel: (id: string) => Promise = async (id: string) =>{ 88 | return await RNAKakaoSDK.openChannel(id); 89 | }; 90 | 91 | export const openChannelChat: (id: string) => Promise = async (id: string) =>{ 92 | return await RNAKakaoSDK.openChannelChat(id); 93 | }; 94 | 95 | const app: KakaoSDK = { 96 | init, 97 | isInitialized, 98 | getAccessToken, 99 | getProfile, 100 | login, 101 | manualLogin, 102 | loginWithNewScopes, 103 | logout, 104 | unlink, 105 | openChannel, 106 | openChannelChat, 107 | }; 108 | 109 | export default app; 110 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import {KakaoSDK, ProfileType} from './types'; 2 | 3 | declare const global: Window & typeof globalThis; 4 | 5 | const getKakaoSDK = (): Promise => { 6 | return new Promise((resolve, reject) => { 7 | if (typeof window === 'undefined') reject({message: 'unsupported platform'}); 8 | const kakaoSDK = global.Kakao; 9 | if (kakaoSDK) { 10 | resolve(kakaoSDK); 11 | return; 12 | } 13 | 14 | const jsapi = document.createElement('script'); 15 | jsapi.type = 'text/javascript'; 16 | jsapi.src = 'https://developers.kakao.com/sdk/js/kakao.min.js'; 17 | const s = document.getElementsByTagName('script')[0]; 18 | s?.parentNode?.insertBefore(jsapi, s); 19 | jsapi.onload = () => resolve(global.Kakao); 20 | jsapi.onabort = jsapi.onerror = reject; 21 | }); 22 | }; 23 | 24 | export const init = async (appKey: string) => { 25 | const kakao = await getKakaoSDK(); 26 | if (!kakao.isInitialized()) kakao.init(appKey); 27 | }; 28 | 29 | export const isInitialized = async () => { 30 | const Kakao = await getKakaoSDK(); 31 | return Kakao.isInitialized(); 32 | }; 33 | 34 | export const login = async () => { 35 | const Kakao = await getKakaoSDK(); 36 | const exec = () => new Promise((success, fail) => Kakao.Auth.login({scope: '', success, fail})); 37 | const output: any = (await exec()) || {}; 38 | return { 39 | access_token: output?.access_token, 40 | expires_in: output?.expires_in, 41 | refresh_token: output?.refresh_token, 42 | refresh_token_expires_in: output?.refresh_token_expires_in, 43 | scopes: output?.scope?.split(' '), 44 | token_type: output.token_type, 45 | }; 46 | }; 47 | 48 | export const loginWithNewScopes = async (scopes: string[]) => { 49 | const Kakao = await getKakaoSDK(); 50 | const scope = scopes?.join(','); 51 | const exec = () => new Promise((success, fail) => Kakao.Auth.login({scope, success, fail})); 52 | const output: any = (await exec()) || {}; 53 | return { 54 | access_token: output?.access_token, 55 | expires_in: output?.expires_in, 56 | refresh_token: output?.refresh_token, 57 | refresh_token_expires_in: output?.refresh_token_expires_in, 58 | scopes: output?.scope?.split(' '), 59 | token_type: output.token_type, 60 | }; 61 | }; 62 | 63 | export const logout = async () => { 64 | const Kakao = await getKakaoSDK(); 65 | if (!Kakao.Auth.getAccessToken()) throw {message: 'Not logged in.'}; 66 | 67 | const exec = () => new Promise(success => Kakao.Auth.logout(success)); 68 | await exec(); 69 | }; 70 | 71 | export const unlink = async () => { 72 | const Kakao = await getKakaoSDK(); 73 | const url = '/v1/user/unlink'; 74 | const exec = () => new Promise((success, fail) => Kakao.API.request({url, success, fail})); 75 | await exec(); 76 | }; 77 | 78 | export const getAccessToken = async () => { 79 | const Kakao = await getKakaoSDK(); 80 | const output: any = Kakao.Auth.getAccessToken(); 81 | return { 82 | id: output?.id, 83 | expires_in: output?.expires_in, 84 | }; 85 | }; 86 | 87 | export const getProfile = async (): Promise => { 88 | const Kakao = await getKakaoSDK(); 89 | const url = '/v2/user/me'; 90 | const exec = () => new Promise((success, fail) => Kakao.API.request({url, success, fail})); 91 | return await exec() as ProfileType; 92 | }; 93 | 94 | export const openChannel: (id: string) => Promise = async (channelPublicId: string) => { 95 | const Kakao = await getKakaoSDK(); 96 | return Kakao.Channel.addChannel({channelPublicId}); 97 | } 98 | 99 | export const openChannelChat: (id: string) => Promise = async (channelPublicId: string) => { 100 | const Kakao = await getKakaoSDK(); 101 | return Kakao.Channel.chat({channelPublicId}); 102 | } 103 | 104 | const app: KakaoSDK = { 105 | init, 106 | isInitialized, 107 | getAccessToken, 108 | getProfile, 109 | login, 110 | loginWithNewScopes, 111 | logout, 112 | unlink, 113 | openChannel, 114 | openChannelChat, 115 | }; 116 | 117 | export default app; 118 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import app from './app'; 2 | 3 | const { name, version, homepage } = require('../package.json'); 4 | console.log(` 5 | ___ ______________ ___ ________ 6 | / _ |/ ___/_ __/ _ )/ _ | / __/ __/ 7 | / __ / /__ / / / _ / __ |_\\ \\/ _/ 8 | /_/ |_\\___/ /_/ /____/_/ |_/___/___/ 9 | 10 | ${name} ${version} :: Actbase Opensources. 11 | Contact us -> project@actbase.io 12 | > https://actbase.io/opensource 13 | > ${homepage} 14 | `); 15 | 16 | export const KakaoSDK = app; 17 | 18 | export default KakaoSDK; 19 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface ProfileType { 2 | connected_at: string; 3 | id: number; 4 | kakao_account: { 5 | has_age_range?: boolean; 6 | age_range_needs_agreement?: boolean; 7 | age_range?: 8 | | '0~9' 9 | | '10~14' 10 | | '15~19' 11 | | '20~29' 12 | | '30~39' 13 | | '40~49' 14 | | '50~59' 15 | | '60~69' 16 | | '70~79' 17 | | '80~89' 18 | | '90~' 19 | | null; 20 | 21 | has_birthday?: boolean; 22 | birthday_needs_agreement?: boolean; 23 | birthday?: string; 24 | birthday_type?: string; 25 | 26 | has_birthyear?: boolean; 27 | birthyear_needs_agreement?: boolean; 28 | birthyear?: string; 29 | 30 | has_email?: boolean; 31 | email_needs_agreement?: boolean; 32 | email?: string; 33 | 34 | is_email_valid?: boolean; 35 | is_email_verified?: boolean; 36 | 37 | has_gender?: boolean; 38 | gender_needs_agreement?: boolean; 39 | gender?: 'male' | 'female' | null; 40 | 41 | profile_needs_agreement?: boolean; 42 | profile?: { 43 | is_default_image?: boolean; 44 | nickname?: string; 45 | profile_image_url?: string; 46 | thumbnail_image_url?: string; 47 | }; 48 | 49 | ci_needs_agreement?: boolean; 50 | ci?: string; 51 | ci_authenticated_at?: string; 52 | 53 | legal_birth_date_needs_agreement?: boolean; 54 | legal_birth_date?: string; 55 | 56 | legal_gender_needs_agreement?: boolean; 57 | legal_gender?: 'male' | 'female' | null; 58 | 59 | legal_name_needs_agreement?: boolean; 60 | legal_name?: string; 61 | 62 | phone_number_needs_agreement?: boolean; 63 | phone_number?: string; 64 | }; 65 | properties: { 66 | [key: string]: any; 67 | }; 68 | } 69 | 70 | export interface AccessTokenType { 71 | access_token: string; 72 | expires_in: number; 73 | refresh_token: string; 74 | refresh_token_expires_in: number; 75 | scopes: string[]; 76 | token_type?: string; 77 | } 78 | 79 | export interface AccessTokenInfo { 80 | id: number; 81 | expires_in: number; 82 | } 83 | 84 | export interface KakaoSDK { 85 | init: (appKey: string) => Promise; 86 | isInitialized: () => Promise; 87 | getAccessToken: () => Promise; 88 | getProfile: () => Promise; 89 | login: () => Promise; 90 | manualLogin?: () => Promise; 91 | loginWithNewScopes: (scopes: string[]) => Promise; 92 | logout: () => Promise; 93 | unlink: () => Promise; 94 | openChannel: (id: string) => Promise; 95 | openChannelChat: (id: string) => Promise; 96 | } 97 | -------------------------------------------------------------------------------- /src_bin/install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const { exec } = require('child_process'); 5 | const readline = require('readline'); 6 | const plist = require('plist'); 7 | 8 | const readFile = path => { 9 | return new Promise((resolve, reject) => { 10 | fs.readFile(path, 'utf8', (err, data) => { 11 | if (err) { 12 | reject(err); 13 | } else { 14 | resolve(data); 15 | } 16 | }); 17 | }); 18 | }; 19 | 20 | const writeFile = (path, content) => { 21 | return new Promise((resolve, reject) => { 22 | fs.writeFile(path, content, 'utf8', (err, data) => { 23 | if (err) { 24 | reject(err); 25 | } else { 26 | resolve(data); 27 | } 28 | }); 29 | }); 30 | }; 31 | 32 | const readDir = path => { 33 | return new Promise((resolve, reject) => { 34 | fs.readdir(path, 'utf8', (err, data) => { 35 | if (err) { 36 | reject(err); 37 | } else { 38 | resolve(data); 39 | } 40 | }); 41 | }); 42 | }; 43 | 44 | const execute = cmd => { 45 | return new Promise((resolve, reject) => { 46 | exec(cmd?.replace(/\n/g, ' '), (err, stdout, stderr) => { 47 | if (err) { 48 | console.log(err); 49 | reject(err); 50 | } else { 51 | resolve({ stdout, stderr }); 52 | } 53 | }); 54 | }); 55 | }; 56 | 57 | const question = query => { 58 | return new Promise(resolve => { 59 | const rl = readline.createInterface({ 60 | input: process.stdin, 61 | output: process.stdout, 62 | }); 63 | 64 | rl.question(query, data => { 65 | resolve(data); 66 | rl.close(); 67 | }); 68 | }); 69 | }; 70 | 71 | const start = async () => { 72 | let path = process.env.PWD; 73 | while (true) { 74 | if (fs.existsSync(path + '/package.json')) break; 75 | if (path.length < 10) { 76 | throw { message: 'not found package.json' }; 77 | } 78 | path = path.substring(0, path.lastIndexOf('/')); 79 | } 80 | 81 | const pkg = JSON.parse(await readFile(path + '/package.json')); 82 | if (!pkg.dependencies['@actbase/react-kakaosdk']) { 83 | await exec(`npm i @actbase/react-kakaosdk`); 84 | // throw { 85 | // message: 'no installed @actbase/react-kakaosdk', 86 | // }; 87 | } 88 | 89 | if (pkg.dependencies['react-native']) { 90 | console.log('React Native에 맞춰서 설정을 시작합니다.'); 91 | const input = await question( 92 | '네이티브 앱 키를 입력하세요. (빈칸이면 기본설치 / TEST 키도 추가하시려면 ; 로 구분해서 넣어주세요) : ', 93 | ); 94 | const keys = input ? input.split(';') : []; 95 | console.log('iOS 설치 시작'); 96 | 97 | let name = (await readDir(path + '/ios'))?.filter(v => v.endsWith('.xcodeproj'))?.[0]; 98 | name = name.substring(0, name.indexOf('.xcode')); 99 | 100 | { 101 | console.log('1. Pod Install...'); 102 | let podfile = (await readFile(path + '/ios/PodFile')).split('\n'); 103 | const ix = podfile.findIndex(x => x.indexOf('platform :ios') >= 0); 104 | if (ix >= 0) { 105 | podfile[ix] = podfile[ix].replace("'9.", "'11.").replace("'10.", "'11."); 106 | } 107 | await writeFile(path + '/ios/PodFile', podfile.join('\n')); 108 | await execute(`pod install --repo-update --project-directory=${path}/ios`); 109 | } 110 | 111 | { 112 | console.log('2. Info.plist 설정'); 113 | const plists = []; 114 | let pbx = (await readFile(path + `/ios/${name}.xcodeproj/project.pbxproj`)).split('\n'); 115 | const pbxoutput = pbx 116 | .map(v => { 117 | if (v.indexOf('INFOPLIST_FILE') >= 0) { 118 | plists.push(v.substring(v.indexOf('=') + 1).trim()); 119 | } 120 | if (v.indexOf('IPHONEOS_DEPLOYMENT_TARGET') >= 0) { 121 | return v.replace('9.', '11.').replace('10.', '11.'); 122 | } 123 | return v; 124 | }) 125 | .join('\n'); 126 | console.log(' > Deployment 버전 확인 후 낮을 시 올립니다.'); 127 | 128 | await writeFile(path + `/ios/${name}.xcodeproj/project.pbxproj`, pbxoutput); 129 | for (const plistPath of plists?.filter((v, ix) => plists.indexOf(v) === ix)) { 130 | let r = plistPath.substring(0, plistPath.length - 1); 131 | r = r.replace(/\$\(SRCROOT\)/g, ''); 132 | r = r.replace(/\"/g, ''); 133 | 134 | const appPlist = plist.parse(await readFile(path + `/ios/${r}`)); 135 | if (!appPlist.LSApplicationQueriesSchemes) { 136 | appPlist.LSApplicationQueriesSchemes = []; 137 | } 138 | if (!appPlist.LSApplicationQueriesSchemes.includes('kakaokompassauth')) { 139 | appPlist.LSApplicationQueriesSchemes.push('kakaokompassauth'); 140 | } 141 | if (!appPlist.LSApplicationQueriesSchemes.includes('kakaolink')) { 142 | appPlist.LSApplicationQueriesSchemes.push('kakaolink'); 143 | } 144 | 145 | if (!appPlist.CFBundleURLTypes) { 146 | appPlist.CFBundleURLTypes = []; 147 | } 148 | 149 | for (const key of keys) { 150 | if (!appPlist.LSApplicationQueriesSchemes.includes(key)) { 151 | appPlist.LSApplicationQueriesSchemes.push(key); 152 | } 153 | if (!appPlist.LSApplicationQueriesSchemes.includes(`kakao${key}`)) { 154 | appPlist.LSApplicationQueriesSchemes.push(`kakao${key}`); 155 | } 156 | 157 | if (!appPlist.CFBundleURLTypes.find(v => v.CFBundleURLSchemes.includes(`kakao${key}`))) { 158 | appPlist.CFBundleURLTypes.push({ 159 | CFBundleTypeRole: 'Editor', 160 | CFBundleURLSchemes: [`kakao${key}`], 161 | }); 162 | } 163 | } 164 | await writeFile(path + `/ios/${r}`, plist.build(appPlist)); 165 | } 166 | console.log(' > Plist 내 설정값을 지정합니다.'); 167 | } 168 | 169 | { 170 | console.log('3. AppDelegate.m 설정'); 171 | let adf; 172 | let extension = 'm'; 173 | 174 | try { 175 | adf = await readFile(path + `/ios/${name}/AppDelegate.m`); 176 | } catch { 177 | adf = await readFile(path + `/ios/${name}/AppDelegate.mm`); 178 | extension = 'mm'; 179 | } 180 | 181 | if (adf.indexOf('[WithKakaoSDK isKakaoTalkLoginUrl:url]') < 0) { 182 | let adfsplit = adf.split('\n'); 183 | const ix = []; 184 | adfsplit.forEach((x, n) => { 185 | if (x.indexOf('application:') > 0 && x.indexOf('penURL') > 0 && !x.trim().startsWith('//')) { 186 | ix.push(n); 187 | } 188 | }); 189 | 190 | if (ix.length > 0) { 191 | console.log(' > openURL시 이벤트가 삽입되야 합니다.'); 192 | console.log( 193 | ' > "if ([WithKakaoSDK isKakaoTalkLoginUrl:url]) return [WithKakaoSDK handleOpenUrl:url];" 를 openURL 옵션에 넣어주세요', 194 | ); 195 | console.log(' > 자세한 내용은 github install manual 참고하세요.'); 196 | } else { 197 | const pf = adf.substring(0, adf.lastIndexOf('@')); 198 | const cx = ` 199 | - (BOOL) application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { 200 | if ([WithKakaoSDK isKakaoTalkLoginUrl:url]) return [WithKakaoSDK handleOpenUrl:url]; 201 | return YES; 202 | } 203 | `; 204 | const sf = adf.substring(adf.lastIndexOf('@')); 205 | adf = pf + cx + sf; 206 | await writeFile(path + `/ios/${name}/AppDelegate.${extension}`, adf); 207 | } 208 | } 209 | 210 | console.log(' > 저장 완료.'); 211 | } 212 | 213 | console.log('Android 설치 시작'); 214 | console.log('1. AndroidManifest.xml 수정'); 215 | if (keys.length > 0) { 216 | let am = await readFile(path + '/android/app/src/main/AndroidManifest.xml'); 217 | if (am.indexOf('com.kakao.sdk.auth.AuthCodeHandlerActivity') === -1) { 218 | const prefix = am.substring(0, am.indexOf('') + 11); 219 | const dataList = keys.map(key => ``).join('\n'); 220 | const content = `\n\n 221 | 222 | 223 | 224 | 225 | 226 | ${dataList} 227 | 228 | 229 | `; 230 | const suffix = am.substring(am.indexOf('') + 11); 231 | am = prefix + content + suffix; 232 | await writeFile(path + '/android/app/src/main/AndroidManifest.xml', am); 233 | console.log(' > AndroidManifest.xml 수정완료.'); 234 | } else { 235 | console.log(' > AuthCodeHandlerActivity 가 존재합니다. 키를 확인해주세요.'); 236 | } 237 | } else { 238 | console.log(' > Key가 없는 관계로 우선 패스합니다.'); 239 | } 240 | } 241 | 242 | console.log(``); 243 | console.log(`================================================`); 244 | console.log(`설치완료..`); 245 | console.log(`자세한 부분은 github를 참고하세요.`); 246 | console.log(`https://github.com/actbase/react-kakaosdk`); 247 | console.log(``); 248 | }; 249 | 250 | start().catch(e => { 251 | console.warn(e); 252 | process.exit(1); 253 | }); 254 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es3", 5 | "module": "esnext", 6 | "lib": ["dom", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020", "esnext"], 7 | "jsx": "react", 8 | "preserveConstEnums": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "outDir": "./lib", 12 | "baseUrl": "./src", 13 | "sourceMap": true, 14 | "resolveJsonModule": true, 15 | "removeComments": true, 16 | "declaration": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "allowJs": true, 19 | "noImplicitThis": true, 20 | "moduleResolution": "node", 21 | "typeRoots": ["node_modules/@types", "@types"] 22 | }, 23 | "include": ["src/**/*", "@types"], 24 | "exclude": ["node_modules"] 25 | } 26 | --------------------------------------------------------------------------------