予約 Retweet するだけのサービスを作った
冬休みを利用して予約 Retweet するだけの Web サービスを作りました。 以下の URL からアクセスできます。
モチベーション
何らかの告知ツイートなどを夜(業務時間外とか)に RT したいことが時々ありました。
RT だけなのでさっとやればいいっちゃいいのですが、微妙にめんどくさかったり忘れたりすることもありました。
そこで Retweet を予約できたら便利だなと思って作りました。
SocialDoc などを使っても同様のことができるようなのですが、別の機能もたくさんあったりと機能的に too match だったのでもっとシンプルなものが良いなと思い、 予約 RT だけに特化したものを作ることにしました。
技術的なこと
- Vite でプロジェクトを作っています
- TypeScript + React + firebase (firestore, Hosting, Functions) + GCP(Cloud Task)
- シンプルなアプリなので React の状態管理は Hooks だけで行っていて、 Redux は使用していません
予約 RT の仕組み
Cloud Task を軸に行っています。 Cloud Task は指定した時刻に指定した HTTP Endpoint にリクエストを実行するものです。
また、 Cloud Task の各種操作や RT の実行には firebase functions を使っていて以下の HTTP の Endpoint を作っています(わかりやすさを考慮して実際の名前とは異なります)。
- A: scheduleRetweet: リツイートの予約を行う
-
functions.https.onCall
を使って認証しています
-
- B: execRetweet: リツイートを行う
- OIDC トークン で認証を行っています
- firebase からのリクエストではなく GCP からのリクエストになるのでこの方法が適切です
- 具体的な実装は以下参照
- C: deleteSchedule: 予約を取り消す
-
functions.https.onCall
を使って認証しています
-
functions
.runWith({
// コレをつけるとこの Service Account からの呼び出ししか行えなくなる
invoker: "hogehoge@project-id.iam.gserviceaccount.com",
})
.https.onRequest(async (req, res) => {
})
RT の流れとしては
-
ブラウザ(Client)から Tweet の URL や RT 時刻などをパラメータにして Endpoint A を叩きます
-
Endponit A 内で Cloud Task の Task を作ります
- この際に OIDC トークン の認証情報を付加して Task を作成しています
-
2 で作った Task が指定時刻に Retweet 実行用の Endpoint B にリクエストします
-
Endpoin B 内で Retweet の処理を行います
Retwwet の予約を取り消したい場合はブラウザから Endoint C を叩きます。
Retweet する Endpoint の認証について
Retweet する Endpoint は firebase functions の HTTP Endpoint で作ったので外部に公開されてしまうのですが、外部からは叩いてほしくなく何らかの制限を付ける必要がありました。
ブラウザから呼び出す Endpoint は firebase の機能(onCall)に認証を任せる事ができるのですが、 Retweet する Endpoint に関しては Cloud Task からの呼び出しで firebase 以外からのリクエストになるのでどうするか考える必要がありました。
調べたところ firebase functions (Cloud Functions) には認証方法として OIDC トークン(Service Account) を使用する方法以外にも VPC ネットワーク内のみから呼び出すようにするような設定があり、最初はコレを使えば外部から呼び出されないようにできるのではないかと考えました。
しかし、Cloud Task は VPS ネットワーク外にあるものとみなされるためこの方法は使えませんでした。
そこで、 Task 作成時に OIDC Token を付与して、 Retweet する側の Endpoint で invoker
を設定することで外部から叩いた場合には認証エラーになるように制御しました。
今後
サービス名はまだ決まっておらず、デザインも MUI をそのままなのでなんとかしたいと思います。アイコンも仮で作っただけのものなのでもう少しなんとかしたいと思います。
技術的なこともちょこちょこ記事にできたらなと思います。
Discussion