QR Send

スマホとPCのChrome間で、URLやテキストをQRコード1回のペアリングで送り合える自作Chrome拡張。アカウント不要・無料。

使ってみる →

サーバーを立てずにデバイス間通知を作る:ntfy.shでお手軽pub/sub(アカウント不要)

個人開発

サーバーを立てずに「AからBへ通知したい」を実現する

個人開発をしていると、「スマホで何か操作したらPCに通知したい」「バッチ処理が終わったら自分専用の何かに知らせたい」といった、AからBへちょっとした情報を飛ばしたい場面によく出会います。

本格的にやるならWebSocketサーバーを自前で立てたり、FirebaseやPusherのようなBaaSを使ったりする方法があります。ですが、個人開発の小さな用途にそこまでの構成は大げさです。アカウント登録、APIキーの管理、サーバー費用……乗り越えるハードルの割に、やりたいことは「メッセージを1つ送って、もう片方が受け取る」だけだったりします。

そんなときに便利なのがntfy.shです。実際、自作のChrome拡張QR Send(https://qrsend.app)でもこの仕組みを使っています。

ntfy.shの仕組みはシンプル

ntfy.shは無料で使えるpub/sub(publish/subscribe)サービスです。仕組みは驚くほどシンプルで、以下の2つだけで成立します。

  • 送る側: 好きな「トピック名」に向けてPOSTする
  • 受ける側: 同じトピック名にGETする(あるいはSSE/WebSocketで購読する)

アカウント登録もAPIキーの発行も不要です。トピック名は文字列を決めるだけで、事前登録すら要りません。

curlで送信する例です。

curl -d "デプロイが完了しました" https://ntfy.sh/your-topic-name-here

受信側はGETで直近のメッセージをポーリングして取得できます。

curl -s "https://ntfy.sh/your-topic-name-here/json?poll=1"

これだけで、AからBへの通知が成立します。サーバーを立てる必要も、常時起動するプロセスも(受信側のポーリング以外は)必要ありません。

トピック名は「推測困難なランダム文字列」にする

ここで気をつけたいのが、トピック名の決め方です。ntfy.shのトピックは、名前さえ知っていれば誰でもpublish/subscribeできる仕組みになっています。つまりalertsmy-notifyのような分かりやすい名前にしてしまうと、同じ名前を使った他の人のメッセージが届いたり、逆に自分のメッセージを他人に見られたりする可能性があります。

そこで、crypto.randomUUID()のようなAPIで生成した長いランダム文字列をトピック名にします。

const topic = crypto.randomUUID();
// 例: "f47ac10b-58cc-4372-a567-0e02b2c3d479"

これだけの長さの文字列を偶然言い当てられる確率は実質ゼロなので、URLを知っている人だけが読み書きできる「実質的にプライベートな通知チャンネル」として使えます。

ただし、ここは正直に書いておく必要があります。ntfy.shの無料枠のトピックは、暗号化された非公開チャンネルではなく、あくまで「名前を知っていれば誰でもアクセスできる公開のエンドポイント」です。ランダムな名前によって推測されにくくなっているだけであり、本質的には公開の場所だという前提を忘れてはいけません。パスワードやAPIキー、個人情報など、漏れて困る情報を流すのはやめておきましょう。あくまで「URLの中身」や「処理が終わったことを知らせるだけの短いメッセージ」など、万一見られても実害が小さいものに使うのが安全な運用です。

JavaScript(fetch)での送信・購読の例

ブラウザやChrome拡張から使う場合は、fetchだけで完結します。送信側です。

async function publish(topic, message) {
  await fetch(`https://ntfy.sh/${topic}`, {
    method: "POST",
    body: message,
  });
}

publish("f47ac10b-58cc-4372-a567-0e02b2c3d479", "PC作業が完了しました");

受信側は一定間隔でポーリングして、新着があれば処理する形にします。

let lastMessageId = null;

async function pollOnce(topic) {
  const res = await fetch(`https://ntfy.sh/${topic}/json?poll=1&since=5m`);
  const text = await res.text();

  const messages = text
    .trim()
    .split("\n")
    .filter(Boolean)
    .map((line) => JSON.parse(line));

  for (const msg of messages) {
    if (msg.id === lastMessageId) continue;
    lastMessageId = msg.id;
    console.log("受信:", msg.message);
  }
}

setInterval(() => pollOnce("f47ac10b-58cc-4372-a567-0e02b2c3d479"), 30000);

since=5mを付けているのは、起動直後に古いメッセージまで大量に取得してしまわないようにするためです。ポーリング間隔は用途次第ですが、個人利用の通知であれば30秒程度でも十分実用的です。リアルタイム性を求めるならSSEやWebSocketでの購読もできますが、個人開発の軽い用途ならシンプルなポーリングで困ることはほとんどありません。

メッセージは一定時間で消える

ntfy.shに溜まったメッセージは、無料枠では一定時間が経過すると自動的に削除されます。つまりこのサービスは「ずっと残るデータベース」ではなく、「今すぐ届けるための一時的な中継所」だと考えるのが正確です。

これは制約であると同時に、実は都合の良い性質でもあります。履歴を自分で管理・削除する手間を気にせず使い捨てのメッセージを流せるからです。逆に言えば、後から参照したい記録や重要なログを保存する用途には向いていません。「今すぐ相手に届けばよい、届かなかったら諦めてよい」という通知にこそ向いたサービスです。

まとめ

  • ntfy.shは、トピック名にPOSTするだけで送信、GETするだけで購読できる無料のpub/subサービス
  • アカウント登録もサーバー構築も不要で、個人開発の「AからBへ通知したい」を数行のコードで実現できる
  • トピック名はランダムな文字列にすることで実質的にプライベートなチャンネルとして使えるが、本質は公開のエンドポイントなので機密情報は絶対に送らない
  • メッセージは一定時間で自動的に消えるため、恒久的な保存には向かず、あくまで一時的な通知の中継として使うのが適切
  • curlでもfetchでも数行で試せるので、個人開発の小さな通知の仕組みに困ったら気軽に試してみてほしい

コメント

タイトルとURLをコピーしました