Blueskyでブックマークを実現するためのフィードです。Cloudflare Workers®︎で動作するサーバーレスアプリケーションで、hono を使った簡潔なコードで実現しています。
- 他のユーザーに見られないプライベートなブックマーク
- クラウドエッジで動作する高速な応答性
- 機密情報を保持しない安全な設計
- Web Share Target APIを活用したPWA
- トップページでトークンを取得
- iOSショートカットをインストール
- 共有メニューからBlueskyのポストをブックマーク
- ブックマークフィードを更新
- トップページでトークンを取得
- WebページをPWAとしてホーム画面に追加
- 共有メニューからBlueskyのポストをブックマーク
- ブックマークフィードを更新
- botやspamなど一部のユーザーは利用できません
- 一人当たりのブックマーク数の上限があり、上限に達すると追加ができなくなります
field | name | type |
---|---|---|
form | handle | string |
form | password | string |
ハンドルネームとアプリパスワードを入れてブックマークを編集するトークンを取得します。
Caution
トークンの管理は厳重に行ってください。万が一流出すると、他人にブックマークが追加されたり削除されたりします(このトークンでブックマークの閲覧はできません)。
field | name | type |
---|---|---|
header | Authorization | Bearer token |
form | url | URL |
ブックマークを追加します。成功すると201ステータスコードでJSONのレスポンスが返ってきます。
field | name | type |
---|---|---|
header | Authorization | Bearer token |
form | url | URL |
ブックマークを削除します。成功すると200ステータスコードでJSONのレスポンスが返ってきます。
この Deploy ボタンをクリックしてCloudflare Workersプロジェクトを作成します。
wrangler login
をPCで実行します。- フォークしたリポジトリをcloneしてきます。
- D1データベースを次のコマンドで作成します
wrangler d1 create bluebookmark
すると以下のような出力が現れます:
⛅️ wrangler 3.28.2
-------------------------------------------------------
✅ Successfully created DB 'bluebookmark' in region APAC
Created your database using D1's new storage backend. The new storage backend is not yet recommended for production workloads, but backs up your data via point-in-time
restore.
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "bluebookmark"
database_id = "355b4c9e-a40f-4d4a-9a2d-f474b1d3d727"
database_id
と書かれた行をコピーして wrangler.toml にある database_id の値と置き換えます。- KVネームスペースを次のコマンドで作成します
wrangler kv:namespace create did_key_store
すると以下のような出力が現れます:
⛅️ wrangler 3.28.2
-------------------------------------------------------
🌀 Creating namespace with title "bluebookmark-did_key_store"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "did_key_store", id = "0267def52a42498ebfb9f5de18ad4f84" }
id
の値をコピーして wrangler.toml にある id の値と置き換えます。- 次のコマンドでD1データベースを初期化します
wrangler d1 execute bluebookmark --file=drizzle/0000_productive_riptide.sql
- wranger.toml ファイルの変更を
git commit
でコミットします。
変更をGitHubにプッシュするとGitHub Actionsによって自動でデプロイされます。ローカルで pnpm run deploy
を実行することでもできます。
次の変数をGitHub Actionsのsecrets and variablesに設定する必要があります。
変数名 | 説明 |
---|---|
CF_ACCOUNT_ID | Cloudflare の account ID |
CF_API_TOKEN | Cloudflare の API token |
FEED_HOST | feedを実行するドメイン名 |
FEED_OWNER | Blueskyのハンドル名 ("@"マーク以降) |
JWT_SECRET | セッションを保護するためのランダム文字列 |
APP_PASSWORD | カスタムフィードの公開のためのBlueskyのアプリパスワード |
カスタムフィードをデプロイできたらBlueskyに公開します。
GitHub Actionsのワークフローから "Publish Feed" を選択し "Run workflow" ボタンをクリックします。
または、 FEED_HOST と FEED_OWNER と APP_PASSWORD 環境変数をセットして node ./scripts/publish-feed.js
を実行します。
次のような内容の .dev.vars という名前のファイルをプロジェクトの最上位ディレクトリに作ります。
JWT_SECRET=jwt-secret
FEED_OWNER=mzyy94.com
FEED_HOST=bluebookmark-feed.example.com
初回のみ pnpm run init:local
を実行してください。
pnpm run dev
でローカルサーバーが立ち上がります。
静的HTMLファイルをCloudflare Pagesから配信したい場合、wrangler pages project create <任意のプロジェクト名>
を一度実行し wrangler pages deploy public
で変更をデプロイしてください。
CIでCloudflare Pagesにデプロイするには、プロジェクト名を PAGES_PROJECT
environment secretにセットしてください。
- アプリパスワードはユーザーの利用可能権限の確認のためのみに用いられ保存されることはありません
- データベースは暗号化されていないため管理者はデータを平文で見える状態になっています
- デプロイ可能な実装を公開しているため、プライバシーに懸念のある人は誰でも個人でサービスをセットアップできます
このブックマークフィードではブックマークを非公開とするためにフィードにアクセスしに来るユーザーを厳格に検証しています。
Blueskyのサーバーで署名されたトークンが付与されたリクエストが正しく検証された場合にのみブックマークが表示される仕組みです。
今の実装では検証のための認証フォーマットは secp256k1
のみサポートしています。
2024年2月現在は、 bsky.social は secp256k1
を認証キー(Multikey)として利用しているため問題は起きないはずですが、これ以外の認証キーを持つユーザーはブックマークフィードが空の表示になることがあります。
サービス間認証について詳しくは、 AT Protocolの XRPC と Cryptography ページを参照してください。
sequenceDiagram
autonumber
actor User as ユーザー
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Cloudflare: /api/register へリクエスト
Note left of Cloudflare: ハンドルネームとアプリパスワード
Cloudflare->>Bluesky: XRPC createSession
Note right of Cloudflare: ハンドルネームとアプリパスワード
Bluesky->>Cloudflare: did と accessToken を返却
Cloudflare->>Bluesky: XRPC getProfiles
Bluesky->>Cloudflare: label と viewer情報 を返却
Cloudflare->>Cloudflare: ユーザーを受け入れられるかチェック
break もし不適格なユーザーであれば
Cloudflare->>User: forbidden を返却
end
Cloudflare->>Cloudflare: 公開鍵をキャッシュ
Cloudflare->>User: トークン を返却
Note left of Cloudflare: トークン
sequenceDiagram
autonumber
actor User as ユーザー
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Cloudflare: /api/bookmark へリクエスト
Note left of Cloudflare: 投稿 URL と Bearer トークン
Cloudflare->>Cloudflare: トークンの検証
break 検証エラー
Cloudflare->>User: unauthorized を返却
end
Cloudflare->>Cloudflare: キャッシュから投稿 uri を探す
opt 見つからなかったら
Cloudflare->>Bluesky: XRPC describeRepo
Note right of Cloudflare: 投稿 URL
Bluesky->>Cloudflare: 投稿 uri を返却
end
Cloudflare->>Cloudflare: データベースにブックマークを保存
Cloudflare->>User: Created を返却
sequenceDiagram
autonumber
actor User as ユーザー
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Cloudflare: /api/bookmark へリクエスト
Note left of Cloudflare: 投稿 URL と Bearer トークン
Cloudflare->>Cloudflare: トークンの検証
break 検証エラー
Cloudflare->>User: unauthorized を返却
end
Cloudflare->>Cloudflare: キャッシュから投稿 uri を探す
opt 見つからなかったら
Cloudflare->>Bluesky: XRPC describeRepo
Note right of Cloudflare: 投稿 URL
Bluesky->>Cloudflare: 投稿 uri を返却
end
Cloudflare->>Cloudflare: データベースからブックマークを削除
Cloudflare->>User: OK を返却
sequenceDiagram
autonumber
actor User as ユーザー
participant Cloudflare as Cloudflare Worker
participant Bluesky
User->>Bluesky: ブックマークカスタムフィードを要求
Bluesky->>Cloudflare: ブックマークカスタムフィードを取得
Note right of Bluesky: 署名済みトークン
Cloudflare->>Cloudflare: キャッシュから 公開鍵 を取り出す
Cloudflare->>Cloudflare: サービス間認証トークンの検証
opt 検証エラーが出た場合、新しい公開鍵を取得して再検証
Cloudflare->>Bluesky: XRPC describeRepo
Bluesky->>Cloudflare: 最新の 公開鍵 を返却
Cloudflare->>Cloudflare: 再検証
Cloudflare->>Cloudflare: 公開鍵 をキャッシュに保存
end
Cloudflare->>Cloudflare: キャッシュからブックマーク済み投稿を取得
opt キャッシュにない場合
Cloudflare->>Cloudflare: データベースからブックマーク済み投稿を取得
end
Cloudflare->>Bluesky: ブックマークした投稿一覧を返却
Bluesky->>User: ブックマークフィードとして投稿一覧を返却
このプロジェクトはMITライセンスで提供されています。