2017年1月6日金曜日

phpでAWSのSNSを使ってpush通知を送るときのパターン的なお話

  • このエントリーをはてなブックマークに追加

アプリを作っているとプッシュ通知を送る必要があるわけで。
もちろんAPNsを直接うんちゃらとかしたりすればよかったりするんだけど、AWSにはAmazon Simple Notification Serviceっていうのがあるわけで。
このSNSが諸々面倒なところを代替してくれるっていう優れたもの。

色々と情報を調べるとこうやればプッシュ通知を送信できる!とかは書いてあるんだけど、
attributesのenabledがfalseになってるものをどう復帰させるとか、そういったプラクティス的なのが全然情報がなく。
ということで今日はSNSを通してpush通知を送る際には、こうやったらいいんじゃないか的なお話をば。

ざっくりとした流れ

1:headerにdeviceTokenみたいな形でiOSのトークンを常に送信
2:アプリのトークンを確認すると同時にデバイストークンをSNSに送信してエンドポイントに変換
 2-1:すでに登録されている場合はそのまま単純に上書きされる
 2-2:enabledがfalseになっているときはエラーが出るので、try catchでキャッチして、disabledフラグを立てる
3:disabledフラグが立っていたらそのエンドポイントをenabledに強制変換
4:全員に送信するためのTopicに登録
5:そのユーザのTopicに登録

require_once("/path/to/hoge/vendor/aws-sdk-php/aws-autoloader.php");
use Aws\Sns\SnsClient;

/* get headers */
$headers = getallheaders();
$appToken = $headers['appToken'];
$deviceToken = $headers['deviceToken'];

/* app token check */
token_is_valid($appToken);

/* endpoint create from deviceToken */
$sns = SnsClient::factory(array(
  'key'    => 'xxxxxxxxxxxxxxxx',
  'secret' => 'xxxxxxxxxxxxxxxx,
  'region' => 'ap-northeast-1'
));

try{
  $options = array(
    'PlatformApplicationArn' => 'ios:arn:***********************',
    'Token' => $deviceToken,
    'CustomUserData' => "user_hoge"
  );
  $res = $sns->createPlatformEndpoint($options);

  $disabled = 0;

  if(!isset($res['EndpointArn'])){
    return false;
  }

  $EndpointArn = $res['EndpointArn'];
}
catch(\Exception $e){
  $message = $e->getMessage();

  preg_match("/.+Endpoint (.+) already.+/",$message,$matches);
  
  if(count($matches) > 1){
    $disabled = 1;
    $EndpointArn = $matches[1];
  }
}

/* endpoint disable -> enable */
if($disabled === 1){
  try{
    $sns->setEndpointAttributes(array(
      'EndpointArn' => $EndpointArn,
      'Attributes' => array(
        'Enabled' => 'true'
      )
    ));
  }
  catch(\Exception $e){
    return false;
  }
}

/* deviceToken add all_user topic */
$topic = $sns->createTopic(array(
  "Name" => "AppName-all_user"
));

$sns->subscribe(array(
  'Endpoint' => $EndpointArn,
  'Protocol' => 'Application',
  'TopicArn' => $topic['TopicArn']
));

/* deviceToken add user_hoge topic */
$myTopic = $sns->createTopic(array(
  "Name" => "AppName-user_hoge"
));

$res = $this->sns->subscribe(array(
  'Endpoint' => $EndpointArn,
  'Protocol' => 'Application',
  'TopicArn' => $myTopic['TopicArn']
));

1:headerに常にdeviceTokenをつける

なんかころころ変わるという噂がもっぱらあるわけだったりするわけで。
それにいつenabledがfalseになるかとかも考えるのが面倒なわけだから常にやっておこうみたいな。


2:デバイストークンをエンドポイントに変換

普通だったらそのまま単純に上書きされて終わりだけど、
enabledがfalseになっているとエラーが出て処理がそこでストップしてしまうので、catchする必要があるわけで。
その際にエラーメッセージから正規表現でエンドポイントを抜き出しておき、disabledフラグを立てておく。


3:disabledフラグが立っていたらenabledにする

enabledに強制変換しておかないともちろん送信ができないよねっていう。


4:全員に送信するためのTopicに登録

プッシュ通知を全員に送りたい場面ってあるわけで。
だからそういうときのために常にall_userっていうTopicに入れておくといいよ的な。


5:個別のTopicに登録

違うデバイスでログインをしたらデバイストークンは別になるわけで。
だからユーザごとのTopicに登録しておきましょう的な。


そもそもTopicって?

Topicの中にはエンドポイントを複数格納できるわけで。
なのでTopicに対してプッシュ通知を発行してあげると、その配下にあるエンドポイント全部に送ってくれるっていう。


$arn = "arn:aws:sns:ap-northeast-1:XXXXXXXXXX:":
$app_name = "AppName";
$to = "user_hoge"; //topic name

$apns = ($env === "production")?"APNS":"APNS_SANDBOX";

$json = array(
  'MessageStructure' => 'json',
  'TargetArn' => "{$arn}{$app_name}{$to}",
  'Message' => json_encode(array(
    "default" => $this->aps['alert']['body'],
    $apns => json_encode(array(
      "aps" => array(
        "alert" => array(
          "title" => "push title",
          "body" => "push body"
        ),
        "sound" => "default",
        "badge" => "default",
        "category" => null
      )
    ))
  ))
);

try{
  $this->sns->publish($json);
}
catch(\Exception $e){}

ってな感じでやってあげればOK。
とりあえずarnは規則性があって、XXX部分はAWSアカウント固有
なのでそれだけconfigファイルに入れておくとかしておけば、上に書いてあるようにできる。
そうすると$to部分をトピック名にしてあげればどのユーザだろうが全員だろうが送れるっていう。

これが多分いいんじゃないかっていうパターン的な。
ちなみにfalseになったままのものを削除するとか考えたんだけど、
面倒だしtopicにして送信することでSNS側で自動的に振り分けてくれるからそうした方がいいんじゃなかっていう。
とりあえずこうすればDB内にエンドポイントを保存する必要がないから楽なんじゃないか的なみたいな。

Adsense