「ストレイテナーWeb通知くん」を作った話
「ストレイテナーWeb通知くん」を作ったので、その振り返りをする。
#ストレイテナー のリポスト除くXの投稿を拾って通知するサービスを作りました。Webアプリをホーム画面に追加してサブスクライブすれば、待ってるだけでテナーの新着情報がプッシュ通知で届くようになります。テナー公式LINEなくなって困ってる方使ってください!https://t.co/8fPXNh5lC6
— RM (@ricemountainer) February 18, 2025
はじめに
事の発端はここで挙げている「ストレイテナー通知くん」である。ストレイテナーの公式LINEがなくなったことにより、受動的な更新情報通知が不可能になった。それに対する不満の声をちょいちょいTwitter上で目にしていたので、「じゃあ作ってみるか」と思い立ち、LINE上でそのサービスを開発した。構成は単純で、IFTTTでストレイテナー公式Twitterアカウントを監視して、発見したらLambda(API Gateway)にWebhook、その中でLINEのMessaging API呼んで、ともだち登録してる人にメッセージ配信、という流れだ。
しかし、LINEアカウントのフリープランでは、「配信件数は月に200件」までという制限がある。この「200件」というのは、一つのメッセージを友達登録している人全員に送信する場合、カウントとしては「1」ではなく「友達登録している人の数」になる。例えば「新着通知です!」というメッセージを、友達登録している人が100人いてそれら全員に送信する場合、1回の送信で100消費するので、月に2通しか送信できないことになる。最初このカウントは、「メッセージ数」であって「配信される側の人数」に影響しないと勘違いしていたので、「200通あれば日平均6~7通は送れるから、リツイート除けばまあなんとかなるかな」と思っていたが、この計算だと実質使い物にならないとわかった。なので、LINE以外の方法で通知する手段を別途開発する必要が出てきた。で、色々探した結果、目を付けたのがWebプッシュ通知であった。
技術スタック、構成面・実装面でのポイントなど
基本的にはLINEの頃と変わっておらず「IFTTTでポーリングしてWebhookで通知」だ。ただWebプッシュ通知するにあたってサブスクライブ元のWebアプリが必要になったため、これをNext.jsで作り、Cloudflare Pagesにホスティング。また、IFTTTのWebhookもLambdaからCloudflareに変更した。Webプッシュ通知にはOneSignalを採用。また、通知したメッセージの保存先としてDBが必要になったので、これをSupabaseにした。というわけでORMにPrisma。
ちなみに、WebアプリはNext.jsだが、内部的にはHonoを使っている。Honoは今回の開発で大好きになった。Next.jsには個人的に飽きが来ており、別のフレームワークを使いたい意図が強まっているので、その意味でもHonoは今後も使っていくと思う。
基本的には構成面・実装面でそんなに凝ったことはしていない。シンプルなWebアプリとAPIだ。ただポイントごとにいくつか語っておきたい部分はあるので、以下に挙げていく。
Webプッシュ通知
Webプッシュ通知は、当初FCM(Firebase Cloud Messaging)を使って自前で(?)行うことを考えたが、色々テストした結果どうにも動作が不安定だった(特にサブスクライブの判定とメッセージが実際に届くかどうか)ので、最終的にはFCMの利用は諦めて、OneSignalに舵を切った。余談だが、その際のテストで溜まった知見を放流したのがQiitaのこの記事である。誰かの参考になればいいと思ってます。
OneSignalは非常に直感的・簡単に実装ができた。FCMを自分で利用していたときに比べても動作は安定しており、またサブスクライバーの確認がGUIで可能など、運用面でも楽になった。公式ドキュメントは英語しかないが、調べれば日本語の記事もいくつか見つかるから、利用にあたって大きな問題にもならないと思う。
- https://zenn.dev/kazhack/articles/43e1ebdd75bef1?redirected=1
- https://zenn.dev/overflow_offers/articles/20230302-one-signal-webpush
FCMでWebプッシュ通知を試して挫折した人間からすると、OneSignalを使ってしまったら、もはやFCMを自分で使うという発想に戻ることはできない。大改悪起きない限りこれでいくと思う。
メッセージ送信について
OneSignalを使ってWebプッシュ通知はREST API使って実装した。fetch
で呼べるのが手軽。Referrenceはこのドキュメントである。なおSDK使っても同じことはできるらしいが試してない。fetch
使った実装のサンプルは以下。
...
const data = {
app_id: appId,
target_channel: 'push',
included_segments: ['Total Subscriptions'],
headings: {
en: 'A new update is comming!',
ja: '新着通知です!',
},
contents: {
en: 'A new update is comming, check out the site!',
ja: param.message,
},
url: webPushActionUrl,
};
const fetchResult = await fetch(ONESIGNAL_API_URI + '/notifications', {
method: 'POST',
headers: {
'Accept' : 'application/json',
'Content-type' : 'application/json',
'Authorization': `Key ${apiKey}`,
},
body: JSON.stringify(data),
});
ポイントとしては、、、
included_segments
フィールドは必須らしく、これ指定しないとエラーで送信できない。['Total Subscriptions']
は「サブスクライブしている全員」を対象として送信する指示であり、基本的にこれでいいと思ってるのだが、ここ読む限りもう少し細かい指示ができるようではある。headings
、contents
のen
フィールドは必須である。たとえ「日本語のメッセージ以外送信する気がない」という場合でもen
フィールドは何かしら設定する必要がある。(未設定だとエラーになる)headings
はそれ自体が必須ではないがcontents
は必須フィールドなので、少なくともcontents.en
は何かしら設定する必要がある。headings
はメッセージの「タイトル」にあたる部分である。なくても送信できるが、その場合、iOSだとここに「OneSignalのアプリ名」が自動設定される。未指定の場合に送信されるプッシュ通知のサンプルはこれ↓
これが個人的にちょっとメッセージの視認性を損ねており、できればこの部分(メッセージの一番上の部分)全体が消えてほしかったのだが、探した限りこれを除去する方法が提供されてなさそうだったので、仕方なくタイトル指定することにした。そのサンプルはこれ↓
その他
- 家にある端末は、個人PC(Windows)以外はすべてApple製品(iOS)なので、手持ちの端末でAndroidへのWebプッシュ通知のテストが出来なかった。仕方ないのでVirtual BoxにAndroidのVM作って、とりあえず最低限動作することは確認したが(なおそれにあたってはこちらの記事が参考になった)、やはり実際のモバイル端末でテストしてみないとなんとも不安だったので、ここは友人の力を借りた。実際に実機で操作してみて初めてわかったこともあるので、これは非常に有益だった。この場を借りて御礼申し上げる。っていってもしょうがないけど。
- OneSignalのWebプッシュ通知は、Free Planでは、「月10,000サブスクライバーまで」という制限はあるが、肝心の「メッセージ通知件数」の制限に関しては明記がない。いろいろ調べたんだが、実際これ特に制限はないらしく、ある意味では「いくらでもメッセージ送り放題」ということになる。すごくね??そんなに??ってレベル。ただ一応Rate Limitはあるので、短時間に膨大な数のメッセージの送信はできない。まあこのサービスでいえばRate Limitに払拭するようなプッシュ通知送信が起きることはないはずなので気にしてないが。とにかく「メッセージ送信件数に上限がない」という点はすごいと思いました(小並感)。
- 余談だが、この関連でOneSignalのFAQ Botに実際に質問してみたんだが、これAIになってるようで割と自然に対話が出来てちょっと感動した。まぁ今の時代当たり前なんだけど、こういう単発の疑問や確認はAIで捌けるようになってきてるんだなというのを改めて実感した実例だった。良いと思います。
- 余談だが、この関連でOneSignalのFAQ Botに実際に質問してみたんだが、これAIになってるようで割と自然に対話が出来てちょっと感動した。まぁ今の時代当たり前なんだけど、こういう単発の疑問や確認はAIで捌けるようになってきてるんだなというのを改めて実感した実例だった。良いと思います。
無限スクロール
配信したメッセージの一覧を、無限スクロールで表示する機能を作りたかった。昔ならreact-infinity-scrollerとか使って作ったものだが、ちょっと探してみたのだが似たようなライブラリが2~3個あるものの、どれも最終メンテから2~3年経過されていて放置気味であり、なんとなく使うのは躊躇われた。調べてみると、最近はこういうライブラリではなく、Intersection Obsrverと、ReactでいえばuseEffect
とuseState
使って自分で実装するのが主流なんだそうだ。それっぽいブログ記事が探せばいくつか見つかる。
- https://zenn.dev/lovegraph/articles/15bf1bfde81b26
- https://www.ey-office.com/blog_archive/2023/06/14/modern-infinite-scroll-seems-to-use-intersectionobserve/
参考にはなったが、丸パクリするには少し手を加える必要があり、面倒だったのでChatGPTに書いてもらった。ほぼその出力をそのままコピペしたのが今動いている機能である。要件さえちゃんと伝えればこれほど期待通りに動いてくれるコードになるというのは驚きである。なのでコードの9割はどうやって動いてるのか俺も知らない。こういうアプリが今後増えてくるんだろうなあ、としみじみと実感した事例であった。
Cloudflareの防御機構
Webプッシュ通知の配信の起点となる、IFTTTからWebhookへのrequest送信は、Cloudflare(上にホスティングされたAPI)に向けて行われるが、この宛先はPublicに晒されているURLなので、「URLさえ知っていれば誰でもコールできる」状態であった。まあURL知ってるのは俺だけなのであまり気にする必要もないはずだが、なんとなくここをもう少し凝って、必要以外のアクセスをなるべく防御できる方法はないかと考えた。
一番確実なのは、IFTTTの送信元IPアドレスがわかることだった。それさえわかれば、CloudflareのWAFでそのIPアドレス以外からのrequestを全部遮断すれば良い。これが一番オーソドックスなやり方だろう。ただIFTTTは固定のIPアドレスを持たないので (-> “Is there a fixed range of IP addresses for Webhooks?”)、この方策は使えない。IFTTTから一度EC2あたりを経由すれば「IPアドレスで限定」という制御はできなくはないが、面倒だったのでやめた。コストかさむし考えること増えるしアーキテクチャ的には実際イケテないしな、、、
なので次点として、CloudFrontのカスタムヘッダと同じ発想を取り入れることにした。IFTTTではWebhookのコール時にAdditional Headerをセットできるので、これを使ってとあるカスタムヘッダと、値として適当に採番したUUIDv4の値を設定し、Webhookに送信。Webhook側-Cloudflareでは、WAFのセキュリティルールでそのカスタムヘッダの値を参照して、「このパスへのrequestのHTTPヘッダがこの値以外なら全部弾く」といったWAFルールを設定した。設定時のポイントは、「有効なrequestは通す(それに該当した場合以降のルールはすべてSKIP)」「それ以外は全部拒否」と、WAFのセキュリティルールを2つ利用することだった。そうしないと「どれを通してどれを通さないか」の判断がWAF上で適切に動作しない、例えば拒否ルールがないと許可ルールの有無にかかわらず(ヘッダを設定してようがしてまいが)通しちゃう。なので貴重な(?)WAFのセキュリティルールを2つ消費することになってしまったが、まあこれは必要経費だろう。とにかくこれで、アプリにたどり着く前に不適切なrequestをWAFですべて防御できるようになった。勿論ヘッダとトークン知ってればいくらでもpublicコール可能なのだが、さすがにこれは問題ないだろう。そんなことでここはこれで納得することにした。
この件のポイントというか実現したかった事は、 「アプリで防ぐんじゃなくその前段で防ぐ」 だった。カスタムヘッダを設定して送ってる以上、やろうと思えばアプリ側でも弾けるし、最悪それでも良かったんだが、なんとなく「WAFで弾く」というのを実現してみたかった。技術的好奇心によるところが大きいが、仕事への応用を考慮した面もある。実際、思った通りに実現できて個人的にはそこそこ満足している。この辺はもっといろんなパターンを探ってみたいところだ。
リツイート(リポスト)を省く
小ネタである。テナーの公式Xアカウントは、シンペイさんやOJのポストをリポストすることが結構、ある。「新着情報の通知」をするうえで、これらはノイズになると考えたので(これはLINE版を作ったころから同じ)、Webhook側で「リポストを除く」という処理を入れている。処理は単純で、IFTTTからは投稿内容がテキストとして送られてくるのだが、リポストの場合は先頭に"RT"
という文字列が含まれているので、
export function startsWithRetweet(text:string) {
// 正規表現: "RT @"の後にユーザー名(英数字またはアンダースコア)が1文字以上続く
const retweetPattern = /^RT @\w+/;
return retweetPattern.test(text);
}
で弾いているだけだ。ただ今更になって気づいたが、Xになってから「リポスト」という名称に変わったのに内部的に保持しているテキストは"RT"(リツイート)のままなので、もしかしたらそのうちしれっとここ改修される(“RP"になる?)のかも、と思ったりした。そしたら直すしかないが。。まぁ「テキストの内容で判断」というのがそもそもイケてなく、「リポスト」かどうかを示す属性が独立して存在していてほしいのが理想なのだが、昔の記憶だと、単一のツイート(status)のオブジェクトにはそれを示す属性はなくて、付随する子オブジェクトに「これは何に対するリツイートだよ」というのが設定されている、というような状態だったと記憶しているので、仮にそうやってリポストかどうかを判定できるとしても、IFTTTはWebhookに流してくれない雰囲気を感じる。これはIFTTTっていうよりX(Twitter)に左右される部分が大きいと思うが。X(Twitter)まわりは色々と世知辛い。
RSS(未遂)
RSSも作ろうかなぁとふと思った(のだが現状未遂に終わっており、今の所積極的に作る気もない)。RSSがあればWebプッシュ通知なんか使わずとも自分で好きなRSSリーダー使って更新通知出来るわけで。と、思ったのだが、RSSリーダーの仕様上おそらくポーリングして更新の有無をチェックすることになり、ということはRSSはrequestがあるたび毎回最新情報を取得して返すようにしなければならなくなるだろう。そうなるとrequestの制限に引っかかる可能性が今よりグッと高くなるし、キャッシュも(あまり)できないからCloudflare使ってる意味を感じない。それに、仮に作ったとしても、構成上おそらくRSSリーダー側でそれを検知するのは実際のRSSの更新よりも「後」なので、Webプッシュ通知より情報の即時性がない。なんか諸々あんまりイケてないなぁ、と思ってちょっと躊躇っている。おそらくこのアプリにおいてRSSを作る意味はない。ただ つくってみたい というただの技術者好奇心みたいのがあって、もしかしたら作ってしまうかもしれない。
ちなみに「そんなこと言い出したらTwitter本体の公開RSSを読みこんだらそれでいいんじゃね(このアプリいらなくね)」という話があるのだが、色々調べてみると昨今の改悪騒ぎの一貫でTwitterはRSSを終了しているらしく、Third PartyもAPI有料化を理由に順次撤退しており、実質的に機能していないようだ。(以下参考)
そもそも「Twitterが以前はRSSを公開提供していた」という事実を今更知ったのだが、まあもう終わってるので後の祭りである。そもそもその時代ならAPIでもっと色々出来ただろうし、ていうかテナーの公式LINEがあったしこんなの作る必要性すらなかったはずだから考えても意味ないんだけどね…
あと公式にアクセス可能なRSSが仮にあったとして、懸念されるのは「リツイート」がそこに載ってくることだ。(詳細は上述)単なるRSSだとリツイートも普通に乗っかってきてしまいそうで、そうなると仮にRSSにアクセスできたとして、「各自がRSSリーダーで読み込む」という戦略は、「新着情報だけ知りたい」=公式LINEの代替えになり得るか、という観点では結局成り立たないんじゃないか、という懸念をしている。そこの可能性を考えたり色々検証するくらいなら別ルートで情報発信することを考えたほうが速い(というのもこのWebアプリを作ったきっかけではある)。
なお、ここに来て色々調べたところ、なんかやろうと思えば独自にTwitterのRSSを生成・リードできるようだ。
ただこのやり方はDockerを使うので、コンテナのホスティング環境が必要になるだろう。FargateとかCloud Runとか。それは個人的にはやりたくないし、結局できあがるのは「RSSフィード」であって、それを読み取る(+リツイート除く)のはユーザー側で行う必要があり、手間を考えるとこのWebアプリには適わないだろうな。「ITエンジニアが自分用にRSSで情報収集する手段」という感じなのだろうと予想する。
課題など
(1) あるPOSTが処理されなかった
以下のPOSTが処理されなかった。
【雑誌掲載】
— ストレイテナー (@straightenerjp) February 25, 2025
本日2/25(火)発売
「men's FUDGE」
4月号
ホリエアツシの連載企画「SLAM DRUNK 呑み歩き哉、人生! #20」が掲載されています。
「men's FUDGE」HPhttps://t.co/SnHoZkwJSX@mensFUDGE#ストレイテナー#ホリエアツシ
ここでいう「処理」というのは (1)IFTTT->(2)Cloudflare->(3)Supabase->(4)OneSignal(Webプッシュ通知) という一連の流れを指しているが、うち(3)(4)が動いていない(呼ばれていない)、というのがログからわかった。一方、(2)のログを見てもエラーを出してる様子はなかったので、(2)が意図せず落ちてるとかそういうこともなさそうだった。つまり(2)が「正常系」の範疇で(3)(4)を呼ばない形で処理して終了しているということになる。このケースは「リツイート(リポスト)」がそれに該当するのだが、このPOSTは見てのとおりリポストではない。なぜ「リツイート」扱いになったのか?
これ実は現在でも正確な原因がわかってない。が、状況証拠的に原因じゃないかと疑っている個所について一つの仮説をたてており、それがキャッシュだった。というのも、このPOSTが来る約1時間前にリツイートが発生しているのだ↓
(A)リツイート
(B)処理されるべきPOST
つまりこの(A)「約1時間前にきたリツイート」がキャッシュされてて、次に来た(B)「処理されるべきPOST」のrequestがそのキャッシュからレスポンスされちゃったので、意図せず「リツイート」扱いになって処理されちゃったんじゃないかという予測をたてた。この説を後押しするもう一つの理由として、Honoにキャッシュヘッダの指示を以下のように与えていたことも関連している:
...
cache({
cacheName: 'api-snotifier',
cacheControl: 'max-age: 3600',
})
...
Cache-Control: max-age 3600
だから同一パスのrequestを3600秒=1時間キャッシュする、ということになっていたはずだ。(A)と(B)の間は1時間弱なのでギリギリこのキャッシュ範囲に入っており、この点も個人的にこの仮説に拍車をかけた。とりあえずここが怪しいと踏んで、このキャッシュの部分を必要な箇所ではno-cache
で上書きするように一旦修正した。以後、様子見。
一つ疑問が残る。Cloudflare Workersは「オリジン」のはずで、CDNではないはずだ(という理解でいる)。「リツイートがキャッシュされた」というのがCloudflareのCDN上に起きており、2回目の(B)がそのCDNからキャッシュレスポンスされたというのなら、(B)を「処理した」というログ自体、Cloudflare Workersに記録されないはずだ。実際、これ以外の部分で同じ構成の処理を持っているが、cf-cache-status: HIT
のレスポンスの場合、そっちはそもそもCloudflare Workersに処理ログが記録されない。(CDNのCachec Ruleで処理されてオリジンまでrequestが届かないようになっている。)なのにこのケースでは1回目(リツイート)も2回目(ガチPOST)もCloudflare Workersのログに記録が残っている=つまりどちらもオリジンまでrequestが届いている。なので本当にキャッシュなのか??というのが残る疑問。CDNではなくオリジン自体にキャッシュされていた、、、という可能性もなくはなさそうだがエッジコンピューティングの世界でキャッシュするっていうかキャッシュが使いまわされるというのが個人的にはピンときておらず、自分で自分を納得させられていない。ただ状況証拠見るにここが原因のような気がしてならないので一旦上記の修正を施して様子見の状態である。ここはCloudflareのCDN・Workers・Honoのそれぞれの動作をちゃんと正確に把握しないと裏付けができないので、時間あるときにもう少し深堀してみようと思っている次第である。
そうなると実はIFTTTTのバグ(意図せず同じリクエストを2回Webhookに送っている)っていう可能性もなくはないのだろうか??そっちの線はあまり調べられていなかったが…ここまで書いてて今ふとその可能性に行きついたので、ちょっとその線での調査準備もしておこう。
(2) 画面にデータが表示されない
https://snotifier-app.ricemountainer.net のトップページでは、表示したタイミングで今までに配信したデータを取得してきて、それを描画している。この「配信したデータを取得してきて」の部分がAPIになってて(まあDevtool見れば誰でもわかるけど)、上述した無限スクロールの関係でgetパラメータを変えながら結果が0件になるまで繰り返すっていう仕組みにしていた。これ、初回のデータ取得範囲が「直近3日」だったので、直近3日のうちになんの配信もなかった場合は初っ端のロードが0件になり、無限スクロールの「これで最後まで読み込んだ(=次に読み込むデータがもうない)」条件が速攻で満たされてしまい、即時処理終了、画面に何も表示されない、というケースが発生することに気づいた。要するにアプリのバグですね。。テナー公式Xアカウントは、リポスト除くと最後のポストから次のポストまで3日以上間が空くということは、割とあって、実際2/23~2/27にその現象が発生し、そこでようやく気づいた。というわけで画面側の「無限スクロール」の条件を見直して改修。
この話にはちょっと続きというか関連があり、というのもこのAPIのレスポンスをCloudflareにキャッシュさせているため、例えば (1)1/1~1/4まで配信なし→(2)1/4に誰かが画面ひらく(0件)→(3)1/4に配信が起きる、という流れの場合、(3)は実際にはWebプッシュ通知されてるしDBにも記録されてるのだが、(2)がキャッシュされてるので画面上は何も表示されません、という現象が起き得る。(実際、起きた)画面からデータ取得のために呼び出すAPIは、Cloudflare Workersのrequest数制限もあるので、無駄に乱発させたくないという思いがあって、あまり深いこと考えず「とりま全キャッシュでww」みたいな脳死ムーブをカマしていたんだが、それが仇になった形だ。ちゃんとキャッシュ設計しないとダメだね、という反省点。
(3) 謎のエラーが度々ログに記録される
テスト段階から度々、"Context is not finalized. Did you forget to return a Response object or
await next()?"
というエラーがログに記録されていた。ただこのエラーが発生したからといって別に運用上の大きな問題になっているというわけでもなく、要するに実害がないので放置していたのだが、リリース後もちょいちょい起きるので、気になったので調べてみた。これはHonoが吐いてるエラーで、ソースコードとしてはここに該当する。メッセージを読む限りでは「レスポンスを返却しないまま処理が終わってるぞ」ということを警告するもののようだ。さすがにそんなわけないだろと思っていたが、よく探ったところ、レスポンス返さないまま処理を終了するケースが実際にあった。というわけでアプリのバグである。上の課題(2)よりはるかにしょぼいバグで恥ずかしいのだが、まぁ振り返りの一つとしてここに記録しておくこととする。
(4) その他運用後の課題
- 条件は満たしているはずなのに「subscribeできない」という報告をちょいちょい貰う。報告内容も様々で、「subscribeするかどうかのプロンプトが出てこない」だったり「subscribeしたはずなのにメッセージが届かない」だったりする。前者はもちろん、後者に関しても、「subscribeする部分」で何か想定外のことがあった(結果「subscribeできない」という事象に繋がる)として、subscribeする部分はフロント側で動作するので、サーバーサイドでログ調査ができない点が、課題の原因究明を難しくさせている。subscribeする部分はOneSignalのSDKに完全お任せなのでデバッグもしづらいし、そもそも端末固有の問題だとしたらデバッグ自体できない。何か俺が把握していない条件や問題があるのかもしれず、どうやってアプローチしたもんだか悩んでいる。
- ただ、このアプリは性質上、「ホーム画面に追加してsubscribe」という手順を踏む必要があり、LINEと比べて若干手間がかかるし、単に「通知を受けたい」というニーズを満たすためだけにこの操作をしなければならないというのは、直感的ではないだろうという感覚もなんとなくは分かっており、この辺りの理解が浸透しづらそうで、それの影響もありそうだとも感じている(つまり、例えば「画面開いておけばそれだけでOK」みたいな認識の人がいて、そもそもこの手順が必要と思ってないのが原因なんじゃないか、という勘ぐり)。これはEngineeringとは別の観点の話なのだが、サービスを普及するという目的においては重要な課題であり、仮にこれが原因なのだとしたらそこに対する対応は恐らくコーディングとは全然違う学びがあるとも思う。いずれにせよどこに原因があるのか継続して探っていきたい。
- LINE用の「ストレイテナー通知くん」をShutdownしなければいけない。といいつつ何故か未だに14人くらい友達登録している人がいるのですぐさまShutdownもできないでいる。一応、友達登録時のメッセージに「ストレイテナーWeb通知くんつくったからそっちに移行してね」というメッセージを書いてあるし、月初に同じメッセージを意図的にながしたりもしているが、まあLINEと比べてWebプッシュ通知のsubscribeは若干ながら手間もかかるし、そういう意味でわざわざ移行しようとも思わないだろうしな。少し長い目で見ておく必要がありそうだ、という感じ。それはともかく、作成したこのLINEアカウントを、どうやって削除するのかよくわかっていない。仮に削除したいという時に、友達登録している人がいて削除できるのか?削除できたとして友達登録してる側の人にはどう見えるの?とか、その辺もよくわからない。上記の事情もあってあまり細かく調べてもいないのだが、そうやってるとそのうち完全に忘れてしまうので、時間あるときに調査を進めたいと思っている。
おわりに
というわけで「ストレイテナーWeb通知くん」を作ったという話でした。振り返って見ると、特にFCMを使って実現しようとゴチャゴチャ試行錯誤していた時代のカオスさが思い出される。そのときにはテーブル構造ももっと複雑にするつもりだったし、ホスティングもVercelやGoogle Cloud(Cloud Run)を試したり複数のプラットフォームをまたいだ開発が想定され、割と大掛かりな仕組みになりそうな感じだったんだが、蓋を開けてみると実にシンプルなサービスとして完成にこぎつけており、最初期の計画段階と比較すると「何があったの?」って自分でも思うレベルで変化している。逆に言えば、現状稼働しているものは、見た目は非常にサッパリしており(そもそも凝るつもりもなかったが)、機能もWebプッシュ通知だけという感じで、利用者からすると「こんなカスみたいなやつ必死に作ってたん??」って気になるかもしれない。開発者視点では、最初期の先の見えないトライ&エラーを繰り返していたカオスな計画段階から、よくここまでシンプルにまとまったなという気がしており、「よくやった」というわけではないが単純にその変化に驚いている。「シンプルである」というのは結構良いポイントだと思っているので、この開発の経験や考え方は、次の別の開発にも活かしていきたい。
前にも書いたが、これは結局Twitterの投稿を横流ししているサービスに過ぎず、公式LINEそのものの代替えにはなり得ない。ただ、おそらく当時公式LINEにメッセージを投げていた「大本」が(人間なのかシステムなのか知らないが)いたはずで、そこの向き先をLINEからこのサービスの通知の起点(API)に変更してくれたら、公式LINEとほぼ同じことが実現できそうではあるんだが、そういうわけにもいかないんだろうか。割と真面目にその辺議論したい。お声がけ待ってます。(?)