サイトアイコン PEOPLE Engineering Blog

【Monaca】プッシュ通知をNCMBからBaaS@rakuzaに移行する

ニフクラ mobile backend(NCMB)が、2024/3/31にサービス終了します。

BaaS@rakuzaはNCMBと同じmBaaS(mobile backend as a Service)です。NCMBと同様にプッシュ通知の機能を備えており、移行先としてご利用いただけます。

この記事では、Monaca(Cordova)アプリでプッシュ通知機能を使用している場合に、NCMBからBaaS@rakuzaに移行する方法を解説します。

プラグインについて

Monaca(Cordova)をご利用の場合、monaca_push_pluginを利用しているケースが多いと思いますが、このプラグインはサービス終了後に使用できなくため、別のプラグインを使用する必要があります。

ここでは、cordova-plugin-pushを使った例を紹介します。

cordova-plugin-pushはCordovaでプッシュ通知を実装するためのプラグインです。デバイストークンを取得したり、プッシュ通知を受信してハンドリングを行うことができます。

※Monacaの場合、cordova-plugin-pushはカスタムCordovaプラグインとなるため、有料プラン(Proプラン以上)の契約が必要です。

移行方法について

NCMBの削除とBaaS@rakuzaへの移行を同時に行うと、BaaS@rakuzaにデバイストークンが登録されていないため、移行後にアプリを起動していないユーザーにプッシュ通知を送ることができません。

そのため、移行の前段階としてBaaS@rakuzaを先に組み込んでおき、一定のユーザーのデバイストークンが登録されるまでの間はNCMBと並行運用した方が良いでしょう。

※並行運用が不要であれば、monaca_push_pluginを削除できるため、後述のmonaca_push_pluginに関連した考慮は不要です。
※既存デバイストークンのデータ移行も承っておりますので、問合せください。

BaaS@rakuza SDKの組み込み

BaaS@rakuzaには各プラットフォーム向けのSDKがありますが、本記事ではJavaScript SDKを使ってサンプルコードを書いています。

JavaScript SDKの初期化方法はクイックスタート(JavaScript)を参照ください。

ユーザーの作成

NCMBが端末ごとにデバイストークンを保持するのに対して、BaaS@rakuzaではユーザーごとにデバイストークンを保持します。
そのため、事前にユーザーを作成しておく必要があります。

以下は一度だけユーザーを作成するサンプルコードです。ユーザーを作成するとアクセストークンが返ってくるため、端末に永続化してください(デバイストークンの保存時に使用します)。

// ユーザーアクセストークンを取得(なければ、ユーザーを新規作成)
let userAccessToken = localStorage.getItem("userAccessToken");
if (!userAccessToken) {
  const newUser = await RKZ.User.register({ attributes: {} });
  userAccessToken = newUser.user_access_token;
  localStorage.setItem("userAccessToken", userAccessToken);
}

cordova-plugin-pushのインストール

cordova-plugin-pushのインストール方法はドキュメントのCordovaプラグインを追加するを参照ください。

また、Androidの場合、monaca_push_pluginとcordova-plugin-pushを併用すると、ビルドエラーになります。
そのため、monaca_push_pluginのplugin.xmlを編集して、google_app_idgoogle_api_keyproject_idをstrings.xmlに追加する設定をコメントアウトしてください。

<platform name="android">
    <!-- ... -->
<!--        <config-file parent="/resources" target="res/values/strings.xml">-->
<!--            <string name="google_app_id">@string/google_app_id</string>-->
<!--        </config-file>-->
<!--        <config-file parent="/resources" target="res/values/strings.xml">-->
<!--            <string name="google_api_key">@string/google_api_key</string>-->
<!--        </config-file>-->
<!--        <config-file parent="/resources" target="res/values/strings.xml">-->
<!--            <string name="project_id">@string/project_id</string>-->
<!--        </config-file>-->
    <!-- ... -->
</platform>

デバイストークンの保存

BaaS@rakuzaでもプッシュ通知を送信するためには、端末のデバイストークンを取得してBaaS@rakuzaに保存する必要があります。

デバイストークンの取得はプラグイン(cordova-plugin-push)を使用しますが、monaca_push_pluginもインストールしていると、iOSの場合は片方のプラグインでしかデバイストークンの取得を行えません。

そのため、monaca_push_pluginのAppDelegate+NifCloud.mを編集して、デバイストークンを保存する処理をコメントアウトします。

/**
 * Set up notification.
 * Execute after didFinishLaunchingWithOptions.
 */
- (void)setupNotification:(NSNotification *)notification {
    [NcmbPushNotification setupNCMB];

    // check if received push notification
//    NSDictionary *launchOptions = [notification userInfo];
//
//    if (launchOptions != nil) {
//        NSDictionary *userInfo = [launchOptions objectForKey: @"UIApplicationLaunchOptionsRemoteNotificationKey"];
//
//        if (userInfo != nil){
//            NcmbPushNotification *ncmb = [self getNcmbPushNotification];
//
//            if (ncmb != nil) {
//                [ncmb addJson:[userInfo mutableCopy] withAppIsActive:NO];
//            }
//
//            [NcmbPushNotification trackAppOpenedWithLaunchOptions:launchOptions];
//            [NcmbPushNotification handleRichPush:userInfo];
//        }
//    }
    if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 0, 0}]){
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        center.delegate = self;
    }
}

- (void) registerForRemoteNotifications
{
    [NcmbPushNotification setupNCMB];

//    UIApplication const *application = [UIApplication sharedApplication];
//
//    if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 0, 0}]){
//        //iOS10以上での、DeviceToken要求方法
//        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
//        center.delegate = self;
//        [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert |
//                                                 UNAuthorizationOptionBadge |
//                                                 UNAuthorizationOptionSound)
//                              completionHandler:^(BOOL granted, NSError * _Nullable error) {
//                                  if (error) {
//                                      return;
//                                  }
//                                  if (granted) {
//                                      //通知を許可にした場合DeviceTokenを要求
//                                      dispatch_async(dispatch_get_main_queue(), ^{
//                                          [application registerForRemoteNotifications];
//                                      });
//                                  } else {
//                                      NcmbPushNotification *ncmb = [self getNcmbPushNotification];
//                                      if (ncmb != nil) {
//                                          [ncmb failedToRegisterAPNS];
//                                      }
//                                  }
//                              }];
//    } else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){8, 0, 0}]){
//        //iOS10未満での、DeviceToken要求方法
//        //通知のタイプを設定したsettingを用意
//        UIUserNotificationType type = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
//        UIUserNotificationSettings *setting=  [UIUserNotificationSettings settingsForTypes:type categories:nil];
//        //通知のタイプを設定
//        [application registerUserNotificationSettings:setting];
//        //DeviceTokenを要求
//        [application registerForRemoteNotifications];
//    } else {
//        //iOS8未満での、DeviceToken要求方法
//        [application registerForRemoteNotificationTypes:
//         (UIRemoteNotificationTypeAlert |
//          UIRemoteNotificationTypeBadge |
//          UIRemoteNotificationTypeSound)];
//    }
}

#ifdef __IPHONE_8_0
/**
 * Did register user notifiation settings.
 */
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {

}
#endif

/**
 * Success to regiter remote notification.
 */
//- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
//    NcmbPushNotification *ncmb = [self getNcmbPushNotification];
//
//    if (ncmb != nil) {
//        [ncmb setIsInitialStart];
//        [ncmb setDeviceTokenAPNS:deviceToken];
//    }
//}

/**
 * Fail to register remote notification.
 */
//- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)err{
//    NcmbPushNotification *ncmb = [self getNcmbPushNotification];
//
//    if (ncmb != nil) {
//        [ncmb failedToRegisterAPNS];
//    }
//}

/**
 * Did receive remote notification.
 */
//- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
//    NcmbPushNotification *ncmb = [self getNcmbPushNotification];
//    NSMutableDictionary* receivedPushInfo = [userInfo mutableCopy];
//
//    if (ncmb != nil) {
//        [ncmb addJson:receivedPushInfo withAppIsActive:(application.applicationState == UIApplicationStateActive)];
//    }
//
//    [NcmbPushNotification trackAppOpenedWithRemoteNotificationPayload:userInfo];
//    [NcmbPushNotification handleRichPush:userInfo];
//}

/**
 * Did receive remote notification on ios 10
 */
//- (void)userNotificationCenter:(UNUserNotificationCenter* )center willPresentNotification:(UNNotification* )notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
//
//    completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound);
//}

これで、cordova-plugin-pushでデバイストークンの取得を行うことができます。

cordova-plugin-pushを使ったデバイストークンの取得とBaaS@rakuzaへの保存方法は、ドキュメントのデバイストークンを登録するを参照ください。

なお、デバイストークンは変更があった場合のみ登録することをおすすめします。以下はサンプルコードです。

// デバイストークン取得時のイベント
push.on("registration", async function(data) {
  // デバイストークンが異なる場合、デバイストークンを送信する
  if (data.registrationId !== localStorage.getItem("deviceToken")) {
    // BaaS@rakuzaにデバイストークンを送信する
    await RKZ.User.registerPushDeviceToken(userAccessToken, data.registrationId);

    // デバイストークンを永続化して、次回起動時に比較する
    localStorage.setItem("deviceToken", data.registrationId);
  }
});

NCMB側にデバイストークンを保存する

ただ、この方法だと、NCMB側にAPNsのデバイストークンが保存されなくなってしまいます。
そこで、cordova-plugin-pushで取得したデバイストークンを、直接NCMBに保存する処理を追加します。
monaca_push_pluginには直接デバイストークンを保存する処理は用意されていないため、処理を追加します。

@interface NcmbPushNotification : CDVPlugin {
  // ...
}
// ...
- (void) pushReceived: (CDVInvokedUrlCommand*)command;
- (void) setAPNSDeviceToken: (CDVInvokedUrlCommand*)command; // 追加
@end

- (void)setAPNSDeviceToken:(CDVInvokedUrlCommand *)command {
    NSString *deviceToken = [command.arguments objectAtIndex:0];
    [self setDeviceTokenAPNS:[NcmbPushNotification dataFromHexString:deviceToken]];
    CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}

+ (NSData *)dataFromHexString:(NSString *)string {
    string = [string lowercaseString];
    NSMutableData *data= [NSMutableData new];
    unsigned char whole_byte;
    char byte_chars[3] = {'\0','\0','\0'};
    int i = 0;
    int length = (int) string.length;
    while (i < length-1) {
        char c = [string characterAtIndex:i++];
        if (c < '0' || (c > '9' && c < 'a') || c > 'f')
            continue;
        byte_chars[0] = c;
        byte_chars[1] = [string characterAtIndex:i++];
        whole_byte = strtol(byte_chars, NULL, 16);
        [data appendBytes:&whole_byte length:1];
    }
    return data;
}

NCMB.prototype.setAPNSDeviceToken = function (deviceToken, callback) {
    argscheck.checkArgs('sF', 'NCMB.monaca.setAPNSDeviceToken', arguments);
    callback = callback || function() {};
    exec(callback, null, pluginName, 'setAPNSDeviceToken', [deviceToken]);
};

後は追加した処理を、cordova-plugin-pushのデバイストークン取得時のイベントで呼び出します。

// デバイストークン取得時のイベント
push.on("registration", async function(data) {
  // デバイストークンが異なる場合、各BaaSにデバイストークンを送信する
  if (data.registrationId !== localStorage.getItem("deviceToken")) {
    // NCMBにデバイストークンを送信する
    NCMB.monaca.setAPNSDeviceToken(data.registrationId, function() {});

    // ...
  }
});

これで、NCMBにもデバイストークンが保存されます。

プッシュ通知の送信

BaaS@rakuzaでは、管理画面とAPIからプッシュ通知の送信を行うことができます。

管理画面から送信する場合はドキュメントのプッシュ通知を送信するを、APIから送信する場合はプッシュ通知の配信予約を参照ください。

※2024/2時点では、管理画面から送信できるのはメッセージのみです。タイトルやカスタムペイロードを送信したい場合は、APIをご利用ください。

プッシュ通知の受信

cordova-plugin-pushを使って、受信したプッシュ通知のハンドリングを行うことができます。詳細はドキュメントの受信したプッシュ通知を処理するを参照ください。

ただし、Androidだとmonaca_push_pluginとcordova-plugin-pushのServiceが競合して正しく受信することができません。
そのため、monaca_push_pluginのplugin.xmlを編集して、NCMBFirebaseMessagingServiceの定義をコメントアウトしてください。

<platform name="android">
    <!-- ... -->
<!--        <config-file target="AndroidManifest.xml" parent="/manifest/application">-->
<!--            <service-->
<!--                android:name="com.nifcloud.mbaas.core.NCMBFirebaseMessagingService"-->
<!--                android:exported="false">-->
<!--                <intent-filter>-->
<!--                    <action android:name="com.google.firebase.MESSAGING_EVENT"/>-->
<!--                </intent-filter>-->
<!--            </service>-->
<!--            <meta-data android:name="openPushStartActivity" android:value=".MainActivity"/>-->
<!--            <meta-data android:name="notificationOverlap" android:value="1"/>-->
<!--        </config-file>-->
    <framework src="ncmb.gradle" custom="true" type="gradleReference"/>
    <source-file src="src/android/NcmbData.java" target-dir="src/plugin/push/nifcloud/" />
    <source-file src="src/android/NcmbPushPlugin.java" target-dir="src/plugin/push/nifcloud/" />
</platform>

なお、NCMBからプッシュ通知を送った場合も、cordova-plugin-pushのnotificationイベントで通知のハンドリングを行うことができます。

NCMBサービス終了時

NCMBのサービス終了後は、monaca_push_pluginの各種処理が呼び出されないようにする必要があります。
アプリをアップデートして該当するコードを削除するのが一番良いですが、サーバー側にフラグを持たせて、リモートから制御する方法もあります。

BaaS@rakuzaのアプリ管理機能を使うと、簡単にフラグを使うことができます。

例えば、以下はアプリケーション設定に「NCMBサービス終了フラグ」を追加して、monaca_push_pluginの各種処理を実行されないように制御するサンプルコードです。

// BaaS@rakuzaからアプリケーション設定を取得
const appSettings = await RKZ.appSettings();

// NCMBサービス終了フラグがオフの場合に、デバイストークンを送信する
if (appSettings.attributes.ncmb_end_flg !== "1") {
  NCMB.monaca.setDeviceToken(
    "d985e...",
    "36b76..."
  );
}

// ...

// デバイストークン取得時のイベント
push.on("registration", async function(data) {
  // デバイストークンが異なる場合、各BaaSにデバイストークンを送信する
  if (data.registrationId !== localStorage.getItem("deviceToken")) {
    // NCMBにデバイストークンを送信する
    if (appSettings.attributes.ncmb_end_flg !== "1") {
      NCMB.monaca.setAPNSDeviceToken(data.registrationId, function() {});
    }

    // ...
  }
});

あとは、NCMBサービス終了時に、フラグをオンにすれば移行完了です。

サンプルコード

実際に動作するサンプルコードをGitHubで公開しています。
https://github.com/pscsrv/ncmb-to-baasatrakuza-sample

移行先をご検討中の方は、以下よりお気軽にお問い合わせください。
https://www.raku-za.jp/baas/inquiry/

モバイルバージョンを終了