Things take time

[SWIFT] APNS, 푸시를 보내보자. - Node.js 본문

iOS (기능)

[SWIFT] APNS, 푸시를 보내보자. - Node.js

겸손할 겸 2017. 11. 29. 13:51

[푸시]


푸시를 보낼 때 애플 서버(apns)와 연결하는 서버용 스크립트 파일은 php로도 작성할 수 있지만 node.js로도 사용할 수 있다.

그리고 최근에는 매년 각 앱마다 갱신해야하는 pem파일 대신 통합적으로 이용할 수 있는 p8파일(key)이 개발자사이트에 추가되었다. 이 방법을 이용하여 사용할 것이다.


기본적인 세팅방법은 아래를 참고하는데, 다만 pem파일을 생성하는 부분은 제외한다.


http://g-y-e-o-m.tistory.com/24


푸시 인증서는 키체인에만 등록하는 것으로 마무리 짓는다. (푸시인증서도 다운받지 않아도 될 것 같은데.. 나중에 테스트 해봐야 함)


1. 인증서(프로비저닝, 앱id) 발급 및 등록

2. Xcode에서 Capabilities의 Push Notifications On

3. Pushkit.framework 추가(General 하단 혹은 Build Phases의 Link Binary with Libraries)



[AppDelegate.swift]


안드로이드는 각 뷰(레이아웃)에 대한 생명주기가 있지만, 아이폰은 뷰 컨트롤러뿐 아니라 앱의 생명주기가 있으며 그 앱의 생명주기를 관리하는 파일이 AppDelegate.swift이다.

import UIKit import PushKit import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. /**************************** Push service start *****************************/ // iOS 10 support if #available(iOS 10, *) { UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]){ (granted, error) in if(granted){ print("user push permitted") DispatchQueue.main.async { application.registerForRemoteNotifications() } } else { print("user push not permitted") } } } // iOS 9 and below support else { UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil)) UIApplication.shared.registerForRemoteNotifications() } /***************************** Push service end ******************************/ return true } // APNS서버에 정상적으로 디바이스가 등록되었을 때, 토큰 값과 함께 실행 // application.registerForRemoteNotifications() 이후 콜 func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) print("APNs device token: \(deviceTokenString)") } // Called when APNs failed to register the device for push notifications func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { // Print the error to console (you should alert the user that registration failed) print("APNs registration failed: \(error)") } // 앱이 '켜져있는 상태'에서 푸시 받았을 때(위의 didReceiveRemoteNotification보다 최신 버전, 7.0), 혹은 백그라운드에서 푸시를 클릭해서 들어왔을 때(앱이 꺼진 상태 제어 불가) @available(iOS 7.0, *) public func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Swift.Void){ print("Push notification received: \(userInfo)") if application.applicationState == UIApplicationState.active{ print("켜져있는 상태에서 받음") } else if application.applicationState == .background{ print("background") application.applicationIconBadgeNumber = 1 } else { print("푸시 메시지를 클릭하고 들어옴") application.applicationIconBadgeNumber = 2 guard let info = userInfo["aps"] as? Dictionary

else { return } print("info: \(info)") guard let custom = userInfo["custom"] as? String else{ return } print("custom: \(info)") } } ... }

didfinishLaunch 함수, 즉 앱이 실행되었을 때 사용자에게 푸시를 허용할 것인지에 대한 콜백함수를 호출하고, 되었다면 등록하는 함수를 호출한다.

그리고 fetchCompletionHandler함수에서 푸시를 받은 상태를 제어한다.


여기서 중요한 점 한가지


안드로이드의 경우, FCM의 가이드대로 진행하면 특정 서비스를 매니페스트에 등록하고 이를 정의해두면 앱이 꺼진 상태여도 해당 서비스로 들어오는 애들을 제어할 수 있다. 그렇기 때문에.. 푸시가 왔을 때 네이티브에서 이 푸시를 울리게 할 것인지 말 것인지(서버에서 푸시를 쐈다 하더라도), 진동으로 할것인지 소리로 할 것인지, 푸시가 보여지는 문자열은 어떻게 할 것인지 등등 처리가 가능한 반면, iOS는 실행중인 상태(foreground) 혹은 백그라운드(앱은 실행중이나 홈버튼을 눌러 백그라운드로 돌려진 상태)에서는 푸시를 받자마자 처리가 가능하지만.. 앱이 꺼진 상태에서는 제어가 안된다.


그러므로 서버가 쏘면 무조건 알림은 울리게 되어있다.(사용자가 알림을 허용했다는 가정하에) 이 뜻은 개발자가 디바이스 단에서, 사용자가 푸시를 허용했다 하더라도 푸시받았을 때 안울리게 하고 싶다는 기능을 추가할 수 없다는 뜻이다. (안드로이드는 oNMessageReceived나 sendNotification에서 Noti를 만들어서 처리 가능하다, 예) 특정 시간에 알림 받지 않기)


결론 짓자면, 아이폰에서 푸시를 받았을 때 아이콘에 표시되는 뱃지, 사운드, 푸시 on/off는 무조건 서버에서 모두 다 처리해서 보내야 한다.



[레퍼런스 및 서버 스크립트]


https://github.com/node-apn/node-apn


기본적으로 참고한 사이트다. 노드의 설치는 기본적으로 되어있다고 가정한다.

노드를 실행하고 해당 디렉토리에서 아래의 설치 명령어를 입력한다.

npm install apn --save

var apn = require('apn'); var options = { token: { key: "./경로/AuthKey_Test.p8", keyId: "keyID", teamId: "TeamID" }, production: false }; var apnProvider = new apn.Provider(options); let deviceToken = ["토큰 값"]; var note = new apn.Notification(); // expiry : 전송이 실패하면 지정한 시간까지 다시 전송을 시도함 note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now. note.badge = 3; // sound => 미리 넣고 패키징해야함 note.sound = "Testing.aif", // 메시지 note.alert = "You have a new message"; // Background Mode 사용 시 note.contentAvailable = 1; // 패키지명 note.topic = "패키지명"; note.payload = { custom: "custom messaage" }; apnProvider.send(note, deviceToken).then( (result) => { console.log(result.sent); console.log(result.failed); return; });

Node.js로 작성한 서버 스크립트다.

key는 개발자 사이트에서 발급 받을 수 있으며, 발급 후엔 keyID가 확인 가능하다. Team ID는 아래처럼 개발자 사이트에서 Account를 눌렀을 때 나오는 화면에서 바로 들어가 확인 가능하다.





Background Mode를 사용하려면 contentAvailable이란 것을 1로 해줘야하는데.. 아직 제대로 사용해 본적은 없어, 기본 푸시라면 뺀다.


이렇게 작업을 마치고 푸시를 발송하면