티스토리 뷰

몇 년전 iOS 푸쉬를 사용하고 암 투병생활을 좀 해서 다시는 하고 싶지 않았다. 하지만 회사에서 다시 해야만 하는 상황이 생겼고 이 번에는 React Native(이하 RN) 환경에서 구축하게 되었다. 다행히 암(Push 설정)을 암(RN) 으로 치료하는 기적의 딜 교환으로 이틀 만에 AWS 를 통한 푸쉬 전송을 할 수 있었다. 

현재 사용하고 있는 RN 버전은 아래와 같다.

"react": "16.3.1",
"react-native": "0.55.2"

RN의 버전은 사용할 다른 라이브러리에 맞춰서 좀 낮은 편인 듯 하지만 현재 0.59까지 나온걸 생각하면 괜찮은 편이다.
참고로 RN은 0.0.1 올라갈 때 마다 지각변동이 일어나기 때문에 0.0.1도 쉽게 허용해서는 안된다.

다음으로 react native push 로 구글링하면 바로 등장하는 오픈소스를 사용했다. 
https://www.npmjs.com/package/react-native-push-notification

 

react-native-push-notification

React Native Local and Remote Notifications

www.npmjs.com

이제 첫 번째 난관인 빌드인데 역시나 최신 버전을 가져오니 온같 안드버전을 맞춰달라는 빨간줄 들로 빌드가 되지 않았다. 버전을 조금씩 낮춰가면서 잘 동작하는 2년전 버전인 3.0.2 를 찾을 수 있었다. (나는 안드 sdk 버전을 낮게 유지하기 위해서 이렇게 했지만 sdk 26 을 사용한다면 최신 버전도 동작하지 않을까...?)

"react-native-push-notification": "3.0.2"

빌드를 하고 실제 기기에 설치를 해보니 설치가 되지 않는 현상이 발생했다. virtual device 에서 잘 돌아가고 실제 기기에서 설치가 안되는 걸로 봐서는 왠지 AndroidManifest.xml 의 문제일 듯 싶었다. 라이브러리의 설명에서 추가해 달라는 것을 다 추가했지만 뭐가 문제인지 확인하기 위해 설정을 하나씩 지우다가 <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature" /> 를 삭제하니 정상적으로 설치되었다. 결국 내가 주가한 사항들은 

	<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
	<uses-permission android:name="android.permission.VIBRATE" />
	<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
	<uses-permission android:name="android.permission.WAKE_LOCK" />

	<application
	...
        <receiver
            android:name="com.google.android.gms.gcm.GcmReceiver"
            android:exported="true"
            android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="${applicationId}" />
            </intent-filter>
        </receiver>

        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService" />
        <service
            android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            </intent-filter>
        </service>
	</application>

추가로 android/build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenLocal()
        jcenter()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }
    }
}

android/app/build.gradle (여기는 버전 정보만)

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.westudy.tims"
        minSdkVersion 21
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
    }
   ...
   
dependencies {
    compile project(':react-native-push-notification')
    compile ('com.google.android.gms:play-services-gcm:8.1.0') {
        force = true;
    }
    compile project(':react-native-bluetooth-status')
    compile fileTree(dir: "libs", include: ["*.jar"])
    compile "com.android.support:appcompat-v7:23.0.1"
    compile "com.facebook.react:react-native:+"  // From node_modules
}

RN 빌드가 잘 되는 것을 확인하고 우선 iOS 푸쉬를 설정하기 위해서 메뉴얼을 보니 이 라이브러리는 RN의 PushNotificationIOS 을 사용하고 있었다. RN 이 시키는 데로 xcode 에서 잘 진행한 뒤 이제 대망의 iOS 로 부터 푸쉬 인증서 등록 및 프로비저닝을 다운받는다(라고 쓰고 맥을 구입했는 지 인증받는다 로 읽는다. 네 구입했습니다!)

이 까다로운 절차를 친절하신 개발자분께서 정리해 주셨다. (이 부분 때문에 옛날에 투병생활을 했다..)
https://faith-developer.tistory.com/153

 

iOS Notification GCM Push 구현하기

Notification Push는 스마트폰의 없어서는 안되는 기능 중 하나입니다. 간단하게 Push 기능을 구현한 예제를 공유 하겠습니다. Push 계정 생성 Push 를 사용하려면 Push 계정을 생성해야 합니다. 인증서 생성 키체..

faith-developer.tistory.com

여기까지 하고 폰을 연결해서 xcode 에서 폰에 실행해 보면 위에서 추가한 RN push notification 라이브러리의 onRegister 로 토큰이 전달되는 것을 볼 수 있다.

PushNotification.configure({
	// (optional) Called when Token is generated (iOS and Android)
	onRegister: function (token) {
		console.log('TOKEN:', token);
	},
	...

console log

2019-07-04 14:22:30.086 [info][tid:com.facebook.react.JavaScript] 'TOKEN:', { token: '내토큰',
  os: 'ios' }

여기 까지 했으면 그 힘들다는 리액트에 푸쉬를 붙인것이다! 그럼 발송을 해볼까..

Push 발송 서버를 구축할 수 있지만 AWS 는 아주 저렴한 가격에 푸쉬를 발송해 주기 때문에 나도 여기에 동참했다. 우선 AWS Simple

Notification Service 에 들어가서 좌측 메뉴를 열어보면 모바일 푸쉬가 있는 것을 볼 수 있다.

서울 region 에도 있기 때문에 서울을 선택하면 된다. (서비스가 없는 region을 선택하면 푸시가 안보인다.) 
우측에서 플랫폼 애플리케이션을 생성하는데 일단 push test 를 취해서 iOS sandbox 를 생성했다.

p12 인증서(애플은 대체 얼마나 인증을 받아야 만족할 것인가.) 이 부분은 카카오에 설명이 아주 잘 나와 있다.
https://developers.kakao.com/docs/ios/push-notification

 

Kakao Developers_

더 나은 세상을 꿈꾸고 그것을 현실로 만드는 이를 위하여 카카오에서 앱 개발 플랫폼 서비스를 시작합니다.

developers.kakao.com

생성된 플랫폼 애플리케이션을 목록에서 볼 수 있다. 이 부분의 arn 은 밑에서 사용된다.
이제 AWS 에 엔드포인트(모바일 등록)등록은 api 로 하면 되는데 gradle 에 dependency 를 추가해 주자.

compile ('com.amazonaws:aws-java-sdk-sns:1.11.586')

1. 모바일 푸쉬에 사용되는 토큰을 저장하는 코드는 (AWS. 에 엔드포인트가 생성된다.)

BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
        AmazonSNS amazonSNS = AmazonSNSClient.builder()
                .withRegion("ap-northeast-2")
                .withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
                .build();

        CreatePlatformEndpointRequest request = new CreatePlatformEndpointRequest()
                .withToken("iOS 디바이스 토큰")
                .withPlatformApplicationArn("arn:aws:sns:아까 생성한 플랫폼 애플리케이션의 arn/APNS_SANDBOX/push-test");

        CreatePlatformEndpointResult result = amazonSNS.createPlatformEndpoint(request);
        System.out.println(result.getEndpointArn()); // 이 arn 은 푸쉬를 보낼 때 사용되므로 DB 에 저장하자
}

결과 endpoint arn 은 후에 푸쉬를 보낼 때 사용되므로 저장해 놓자. 그리고 aws access key 와 aws secret key 는 AWS IAM 에서 (이..이제 그만 너무 많아..ㅠㅠ) 생성해야한다. 
AWA IAM 로 이동해서 사용자를 추가하고 

추가된 사용자를 클릭하고 보안 자격 증명 탭으로 이동 후 액세스키 생성 

여기서 secret key 는 한 번만 보여주고 이 후에 볼 수 없으므로 꼭 저장해 두자.

등록된 엔드포인트는 AWS SNS 푸쉬의 플랫폼 애플리케이션을 클릭해서 들어가면 볼 수 있다. 
마지막으로 푸쉬를 보내면 되는데 이때 아까 받은 디바이스에 해당하는 arn 이 필요하다

public void push() throws Exception {
        BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
        AmazonSNS amazonSNS = AmazonSNSClient.builder()
                .withRegion("ap-northeast-2")
                .withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
                .build();

        Map<String, String> data = new HashMap<>();
        data.put("test-data", "test-value");

        Map<String, String> messageMap = new HashMap<String, String>();
        messageMap.put("default", "안녕하세요");
        messageMap.put("APNS_SANDBOX", getAppleMessage("안녕하세요", data));

        PublishRequest request = new PublishRequest()
                .withTargetArn("arn:aws:sns:아까 응답으로 받은 디바이스 arn")
                .withMessageStructure("json")
                .withMessage(new ObjectMapper().writeValueAsString(messageMap));

        PublishResult result = amazonSNS.publish(request);
        System.out.println(result.getMessageId());
    }

    private String getAppleMessage(final String message, Map<String, String> data) throws JsonProcessingException {
        Map<String, Object> appMessageMap = new HashMap<>();
        appMessageMap.put("alert", message);
        appMessageMap.put("badge", 9);
        appMessageMap.put("sound", "default");

        Map<String, Object> appleMessageMap = new HashMap<>();
        appleMessageMap.put("aps", appMessageMap);
        appleMessageMap.put("extra", data); // 부가적으로 전달하고 싶은 값을 전달하기 위해서
        return new ObjectMapper().writeValueAsString(appleMessageMap);
    }

이제 RN push notification 라이브러리 (저 위쪽에서 추가한) 의 onNotification 으로 전달된다.

onNotification: function (notification) {
  console.log('NOTIFICATION: ', notification);
  console.log('extra data: ', notification.data.extra);

  // ios app 실행 중 push 를 에서 받았을 때
  // foreground: true, userInteraction: false,

  // ios app 외부에서 터치로 들어왔을 떼
  // foreground: false, userInteraction: true

  // process the notification
  // required on iOS only (see fetchCompletionHandler docs: https://facebook.github.io/react-native/docs/pushnotificationios.html)
  notification.finish(PushNotificationIOS.FetchResult.NoData);
},

console log

2019-07-04 14:59:35.011 [info][tid:com.facebook.react.JavaScript] 'NOTIFICATION: ', { foreground: true,
  userInteraction: false,
  message: '안녕하세요',
  data: 
   { remote: true,
     notificationId: '6C625AB5-EDFE-4242-97E5-08031ACF3905',
     extra: { 'test-data': 'test-value' } },
  badge: 9,
  alert: '안녕하세요',
  sound: 'default',
  finish: [Function: finish] }
  
2019-07-04 14:59:35.012 [info][tid:com.facebook.react.JavaScript] 'extra data: ', { 'test-data': 'test-value' }

 

아직 좋아하기 이르다..이제 안드로이드를 시작해 보자.
안드로이드는 GCM 이 아닌 FCM 으로 변경이 됐기 때문에 일단 Firebase 에 접속해서 프로젝트를 생성해 준다.
https://firebase.google.com

 

Firebase

Firebase is Google’s mobile platform that helps you quickly develop high-quality apps and grow your business.

firebase.google.com

우측 상단의 콘솔로 이동을 클릭하여 이동하고 프로젝트를 생성해 준다. 기존 프로젝트를 불러올 수 도 있지만 이름을 입력해서 하나 만들어 주었다. (한 번 작업이 금지되었다고 나오고 다시 누르면 생성되는 것은 구글의 컨셉인가...?)
이제 생성된 프로젝트로 이동한 후 프로젝트 설정으로 이동하자

그리고 클라우드 메시지라는 탭으로 이동한다.

여기에서 서버 키는 AWS 에 등록해야 하고 발신자 아이디는 앱의 sender id 에 등록해야 한다.

다시 AWS 로 와서 플랫폼 애플리케이션을 생성하는데 FCM 을 선택해 준다.

그리고 여기 API 키 자리에 위의 서버키를 입력해 준다. 이전 서버키도 동작하지만 그래도 더 긴 걸로 해주자.
iOS 와 같은 방법으로 토큰을 저장해 준다.

    public String registerAndroidToken() {
        BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
        AmazonSNS amazonSNS = AmazonSNSClient.builder()
                .withRegion("ap-northeast-2")
                .withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
                .build();

        CreatePlatformEndpointRequest request = new CreatePlatformEndpointRequest()
                .withToken("안드로이드 토큰")
                .withPlatformApplicationArn("arn:aws:sns:안드로이드 플랫폼 애플리케이션 arn 입력");

        CreatePlatformEndpointResult result = amazonSNS.createPlatformEndpoint(request);
        System.out.println(result.getEndpointArn());
        return result.getEndpointArn();
    }

반환 받은 arn을 통해서 푸쉬를 날려보자

    public void push(String arn) throws Exception {
        BasicAWSCredentials basicAwsCredentials = new BasicAWSCredentials("aws access key", "aws secret key");
        AmazonSNS amazonSNS = AmazonSNSClient.builder()
                .withRegion("ap-northeast-2")
                .withCredentials(new AWSStaticCredentialsProvider(basicAwsCredentials))
                .build();

        Map<String, String> data = new HashMap<>();
        data.put("test-data", "test-value");

        Map<String, String> messageMap = new HashMap<>();
        messageMap.put("default", "안녕하세요");
        messageMap.put("GCM", getAndroidMessage("안녕하세요", data)); // AWS 에 보낼 때는 GCM 으로 보내야 한다.

        PublishRequest request = new PublishRequest()
                .withTargetArn(arn)
                .withMessageStructure("json")
                .withMessage(new ObjectMapper().writeValueAsString(messageMap));

        PublishResult result = amazonSNS.publish(request);
        System.out.println(result.getMessageId());
    }

    private String getAndroidMessage(final String message, Map<String, String> extra) throws JsonProcessingException {
        Map<String, Object> data = new HashMap<>();
        data.put("message", message);

        Map<String, Object> androidMessageMap = new HashMap<>();
        androidMessageMap.put("data", data);
//        androidMessageMap.put("collapse_key", "Welcome");
//        androidMessageMap.put("delay_while_idle", true);
//        androidMessageMap.put("time_to_live", 3600);
//        androidMessageMap.put("dry_run", false);
        return new ObjectMapper().writeValueAsString(androidMessageMap);
    }

안드로이드, iOS 의 getAppleMessage, getAndroidMessage 의 반환값이 String 인 것을 볼 수 있다. 이 부분이 신기한게 json을 만들고 다시 json으로 변환한다.

return new ObjectMapper().writeValueAsString(appleMessageMap); // 이 값을 밑에서 다시 변환한다.

PublishRequest request = new PublishRequest()
	.withTargetArn(arn)
	.withMessageStructure("json")
	.withMessage(new ObjectMapper().writeValueAsString(messageMap)); // 여기서 다시!

iOS 를 보낼 때는 큰 전체를 한 번만 해도 가던게 안드로이드는 꼭 2번을 해줘야 발송된다. 아마도 서버에서 꺼낸 객체를 다시 Object 로 변환하는 것 같다.

머자먼 여정이었다. 다음에는 덜 삽질 하겠지..

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함