← ユースケース

リアルタイムのアプリ内通知

返信、メンション、承認を、発生した瞬間に開いているすべてのタブに表示。ユーザーがオフラインならプッシュ通知に切り替えます。

何かが起きた瞬間にプロダクトが反応すると、ユーザーはそれに気づきます。投稿へのコメント、申請の承認、チームメイトからのメンション。それを 30 秒ごとのポーリングで確かめるのは遅いうえに、リクエストの無駄です。Hibira なら、サーバーが通知を 1 回送るだけで、ユーザーが開いているすべてのタブとデバイスに数ミリ秒で表示されます。オフラインのときはプッシュ通知が引き継ぎます。

ユーザーにとっての価値

  • 何かが起きた瞬間に、開いているすべてのタブでベルのバッジとトーストが更新されます。
  • アプリを閉じているときは、同じ通知がスマートフォンに届きます。
  • 再読み込みしなくても新しいアクティビティが見え、カウンターが古くなりません。

使う機能

  • ユーザー認証(pusher.signin())。ユーザーごとに本人だけが読めるチャンネルを持てます。
  • 公式サーバー SDK の sendToUser。そのユーザーのすべてのソケットに、どのノードからでも届きます。
  • 接続していないユーザーには、Beams 互換 API のプッシュ通知。
  • エンドツーエンドで暗号化したいデータを含む通知には、暗号化プライベートチャンネル(private-encrypted-)。

ステップ 1:ソケット上でユーザーをサインインさせる

チャンネル認可ルートの隣に、ユーザー認証ルートを追加します。サーバー上で動き、アプリのシークレットを使います。

// Express
app.post('/pusher/user-auth', requireLogin, express.urlencoded({ extended: false }), (req, res) => {
  const auth = pusher.authenticateUser(req.body.socket_id, {
    id: String(req.user.id),
    user_info: { name: req.user.name },
  });
  res.json(auth);
});

ブラウザでは JavaScript ガイドのとおりに接続し、サインインします:

const pusher = new Pusher(import.meta.env.VITE_PUSHER_APP_KEY, {
  cluster: 'ap',
  wsHost: 'ws.hibira.io',
  wsPort: 443,
  wssPort: 443,
  forceTLS: true,
  enabledTransports: ['ws', 'wss'],
  userAuthentication: { endpoint: '/pusher/user-auth', transport: 'ajax' },
});
pusher.signin();

pusher.user.bind('notification', (n) => {
  bell.increment();
  toast.show(n.title, { href: n.url });
});

ステップ 2:サーバーから送る

再読み込みしても一覧が残るよう、まず通知をデータベースに保存してから送ります:

const n = await db.notifications.create({ userId, title: 'Ann replied to your comment', url: '/posts/42#c7' });
await pusher.sendToUser(String(userId), 'notification', { id: n.id, title: n.title, url: n.url });

1 回の sendToUser はユーザーのすべてのタブとデバイスに届き、受け取るソケットがいくつあっても 1 メッセージとして数えます。

ステップ 3:オフラインならプッシュに切り替える

サインイン済みのページごとに private-user-42 のようなプライベートチャンネルを購読させ、Webhook のエンドポイントで channel_occupied と channel_vacated を受け取ります。これでサーバーは問い合わせなくても誰がオンラインかわかります:

app.post('/pusher/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const webhook = pusher.webhook({ headers: req.headers, rawBody: req.body.toString() });
  if (!webhook.isValid()) return res.sendStatus(401);
  for (const e of webhook.getEvents()) {
    const id = e.channel.match(/^private-user-(.+)$/)?.[1];
    if (id) await db.users.update(id, { online: e.name === 'channel_occupied' });
  }
  res.sendStatus(200);
});

ユーザーがオフラインなら、プッシュ通知ガイドのとおりにデバイスへ配信します:

if (!(await db.users.isOnline(userId))) {
  await beams.publishToUsers([String(userId)], {
    web: { notification: { title: n.title, deep_link: 'https://your-site.example' + n.url } },
    fcm: { notification: { title: n.title } },
    apns: { aps: { alert: { title: n.title } } },
  });
}

ヒント

  • ペイロードは小さく(ID、タイトル、リンク)して、詳細はユーザーが開いたときに取得しましょう。イベントの上限は 10 KB(Free プランは 4 KB)です。
  • 既読はサーバーで管理し、notifications-read イベントを送れば、すべてのタブのバッジが同時に消えます。
  • 医療、金融、人事のデータは private-encrypted-user-42 チャンネルに送りましょう。Hibira は暗号文を中継するだけです。
  • Free プランでは、アプリの設定でサイトのドメインを許可するオリジンに登録してください。