💬

知識ゼロからSafariのWebPushを実装してみた: 実装編

2023/06/09に公開

ご挨拶

みなさまいかがお過ごしでしょうか。
先日の私は上司との1on1にて「yamashitaさん、今年の3分の1が過ぎたよ!」というお知らせを受けて、ひどく動揺してしまいました😇
(※記事を公開したタイミングでは今年の半分が過ぎていますw)

前回はWebプッシュ通知をFCMでSafari向けに実装してみる、というトライをしましたが、まさかのSafari対応外という事実が実装後に判明、撃沈するオチを迎えました。
https://zenn.dev/xbit/articles/97419981a61b29

上記の記事の後は 少しでも楽をするために ライブラリを選定し直し、ついにSafariからの通知を得ました👏✨

この後はデプロイをしていく予定なのですが、
その前に、今回採用した技術と通知の設計について勉強したので、要約していきます
デプロイ周りが気になる方は、各技術項目もしくはデプロイ編をご参照ください🙌

実装編

Push.jsについて

get start

なんと、npm i push-js でget startは完了です。
https://pushjs.org/docs/quick-start
公式のドキュメントがあっさりしていて非常に読みやすいです。私の冗長な説明は不要なレベル。

通知ステータスについて

今回は使用していませんが、下記のメソッドから通知の許可ステータスが取れます。
ステータスに合わせたコールバックメソッドも用意されていますので、詳細なカスタマイズが可能です。

// 公式から引用
Push.Permission.get();

オプションについて

公式より抜粋、翻訳しました。

オプション 説明
body 通知の本文のテキスト
data ServiceWorker通知に渡すデータ
icon アイコン画像のURLまたは16x16ピクセルと32x32ピクセルのアイコン画像を含む配列。
link モバイルで通知をクリックしたときに移動する相対URLパス
(例:ユーザーをhttp://example.com/pageに移動させたい場合、相対URLはpageになります)。
背面で既に開かれている場合、ブラウザウィンドウは自動的にフォーカスされます。動作にはサーバー上にserviceWorker.jsファイルが必要です。
requireInteraction trueに設定すると、ユーザーが手動で閉じるかクリックするまで通知が閉じません。
tag 通知を識別するための一意のタグ。後で手動で通知を閉じるために使用できます。
timeout 通知が自動的に閉じるまでの時間(ミリ秒単位)
vibrate モバイルデバイスが通知を受信した際に振動するための期間の配列。
例えば、[200, 100]は最初に200ミリ秒振動し、一時停止してから100ミリ秒継続します。モバイル版Chromeでのみ利用可能です。
silent 通知がサイレントであるかどうかを指定します。つまり、デバイスの設定に関係なく、音声や振動を出さないようにします。Chrome 43.0以降でのみサポートされています。

これらのオプションは、自発的にユーザーが通知を開いてくれるようなアプローチを考える場面で有効です。

使用上の注意

今回はアプリの制作者に通知がいけばよいので、細かい使い分けはせず下記のように実装します。

import { ref } from 'vue'
import { useCounterStore } from "../stores/counter"
import Push from 'push.js'

const counter = useCounterStore()
const notificationFlag = ref(false)
const win = window
const increment = () => {
  counter.$patch({ count: counter.count + 1 })
  if(!notificationFlag.value && counter.count > 10) {
    Push.create('筋トレしろ\u{1F4AA}', {
      onClick: function() {
        win.location = 'https://www.youtube.com/results?search_query=%E7%AD%8B%E3%83%88%E3%83%AC%E3%80%80%E8%85%B9%E7%AD%8B'
      }
    })
    notificationFlag.value = true
  }
}

ここで注意したいのが、このまま通知の送信を許可してしまうと、見知らぬ送信者が悪意あるメッセージを送ることができてしまうという点です。

https://developer.mozilla.org/ja/docs/Web/API/Window/postMessage#配信されるイベント

例のように、通知の送付元のドメインチェック処理を入れる必要があります。
気軽にPOSTできないように、サブドメインまでしっかり書いておきたいですね。

// MDN公式から引用
window.addEventListener("message", (event) => {
  // 許可されたドメインからの通知のみ送付する
  // 今回は通知を送信するサーバーのドメインが入る
  if (event.origin !== "http://example.com")
    return;

}, false);

また、今回の要件としては「アプリ開発者に通知を送信する」というものなので、
このままのコードだと「ボタンを押した人が通知を受け取る」までのアプリになってしまいます🌀

今回は割愛しますが、通知用のテーブルを設計して保存し、適宜サーバサイドからpull通知として配信するのが良さそうですね。

サロゲートペア

ライブラリで通知を作成する際、絵文字を使用すると文字化けすることに気が付きます。
これはJavaScriptは文字コードとしてUnicodeを採用し、文字をエンコードする方式としてUTF-16を採用しているため、起きる問題です。

そのため、JavaScriptが解釈できるようにエスケープシーケンスを使用する必要があります。
https://jsprimer.net/basic/string-unicode/

Push.create('筋トレしろ\u{1F4AA}', {
  onClick: function() {
    win.location = 'https://www.youtube.com/results?search_query=%E7%AD%8B%E3%83%88%E3%83%AC%E3%80%80%E8%85%B9%E7%AD%8B';
  }
});

絵文字に対応するUTF-16は下記のようなサイトから調べたり、charCodeAtメソッドから求めることができます💡
https://symbl.cc/jp/

ちょっとした内容ですが、普段は意識していない箇所の言語仕様に触れられて勉強になりました🙌

番外編: 通知という機能の価値について考えてみる

ここで突然ですが、通知の調整については、ユーザビリティに配慮して実装を進める必要があると感じています。(詳しくは前回記事の通知APIという項目をご参照ください🤲)

Reproさんの記事によると、
https://repro.io/contents/mobile-ux-design-what-makes-a-good-notification/

価値をプッシュする

例えば…プッシュ通知に反応することによって、ユーザーにとってどのような価値が得られるか?を狙って通知文言を作成する

通知する時間

例えば…ターゲットのユーザーが活動している時間帯をターゲティングする

入念にテストする

例えば…通知文言を複数用意し、ユーザーのアクションを検証してみる

上記の3つがポイントなようです。
今回は個人開発のアプリですが、今後通知機能を取り入れる際には、気にしておきたいところですね。

本旨とは少し外れてしまいますが、弊社マーケティング担当にお話を軽く伺ったところ、「イベントの設計は多量の分岐があったり、使用している環境に依存する問題もあるので、エンジニアと揉めてしまうこともある😇」そうです。
最初からエンジニア側でも「なぜこの機能が必要なのか」という目的を気にかけておくと、後々の負債は双方にとって軽くなるかもしれませんね💡

コードはこちらで公開しております⏬
https://github.com/ixap2i/push_notification_web

次回はCloudflareを使ってデプロイします☺️


株式会社クロスビットでは、デスクレスワーカーのためのHR管理プラットフォームを開発しています。
一緒に開発を行ってくれる各ポジションのエンジニアを募集中です。

https://x-bit.co.jp/recruit/
https://herp.careers/v1/xbit
https://note.com/xbit_recruit/

クロスビットテックブログ

Discussion