Skip to content

mzyy94/bluebookmark

Repository files navigation

BlueBookmark

Deploy

🇯🇵日本語版READMEはこちら🗾

Bookmark feed for Bluesky, a serverless application running on Cloudflare Workers®︎, using hono and written in simple code.

Features

  • Private bookmarks hidden from other users
  • Fast response time, performed at the cloud edge
  • Secure design with no credentials stored
  • Progressive Web Application with Web Share Target API

Usage

For iOS device

  1. Get a token from the top page
  2. Install iOS shortcut
  1. Bookmark Bluesky post from share menu
  2. Refresh bookmark feed

For Android device

  1. Get a token from the top page
  2. Install PWA
  3. Bookmark Bluesky post from share menu
  4. Refresh bookmark feed

Limitation

  • Restricted to only be available for human account.
  • A limit on how many bookmarks can be added per person, and once the limit is reached, no more bookmarks can be added.

API

POST /api/register

field name type
form handle string
form password string

Enter the Handle Name and App Password to get a token to edit bookmarks.

Caution

Please manage your tokens carefully. If it is leaked, bookmarks will be added or deleted freely (viewing is not allowed with this token).

POST /api/bookmark

field name type
header Authorization Bearer token
form url URL

Add a bookmark. On success, a JSON response is returned with a status code of 201.

DELETE /api/bookmark

field name type
header Authorization Bearer token
form url URL

Delete a bookmark. On success, a JSON response is returned with a status code of 200.

Development

Requirements

  • Bluesky account
  • Cloudflare account
  • GitHub account
  • wrangler
  • pnpm

Create Project

Click Deploy button to create Cloudflare Workers project.

Deploy to Cloudflare Workers

Initialization

  1. wrangler login on your PC.
  2. Clone your forked repository.
  3. Create D1 database with wrangler d1 create bluebookmark And you can see output like below:
⛅️ 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"
  1. Copy a line database_id in output and replace database_id in wrangler.toml
  2. Create KV namespace with wrangler kv:namespace create did_key_store And you can see output like below:
 ⛅️ 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" }
  1. Copy a value of id in output and replace id in wrangler.toml
  2. Initialize D1 database table with wrangler d1 execute bluebookmark --file=drizzle/0000_productive_riptide.sql
  3. Commit changes of wranger.toml file with git commit

Deployment

Push your changes to GitHub and it will be automatically deployed by GitHub Actions. You can also run pnpm run deploy locally.

Environment Secrets

The following variables are required for deployment via GitHub Actions.

Variable Name Description
CF_ACCOUNT_ID Cloudflare account ID
CF_API_TOKEN Cloudflare API token
FEED_HOST Domain on which your feed will be running
FEED_OWNER Bluesky handle name without "@"
JWT_SECRET Random strings to protect sessions
APP_PASSWORD Bluesky app password for Publish Feed use

Publish Feed

After you have deployed the custom feed, publish the feed to Bluesky. Select the "Publish Feed" workflow from GitHub Actions and click "Run workflow". Or, run node ./scripts/publish-feed.js with FEED_HOST, FEED_OWNER and APP_PASSWORD enviroment variables.

Local development

Create .dev.vars file in root directory of this project.

JWT_SECRET=jwt-secret
FEED_OWNER=mzyy94.com
FEED_HOST=bluebookmark-feed.example.com

Run pnpm run init:local only for the first time. Start up local server with pnpm run dev.

Cloudflare Pages (Optional)

If you want to serve static HTMLs from Cloudflare Pages, run wrangler pages project create <YOUR-UNIQUE-PROJECT-NAME> once and wrangler pages deploy public for each change. To deploy to Cloudflare Pages on CI, set the project name to PAGES_PROJECT environment secret.

Privacy

  • The App Password is only used for user identification and availability checks and is not stored, so it is secure.
  • Bookmarks are visible to administrators because the data stored in the database is not encrypted.
  • Designed as open source and deployable implementation, so anyone can setup their own service if they are concerned about privacy.

Inter-Service Authentication

This bookmark feed strictly verifies access to the feed to prevent other users from seeing the bookmarks you have privately saved. The feed content is returned only when a request with a signed token from Bluesky server is successfully verified. At the moment, the only authentication format supported for verification is secp256k1. As of February 2024, bsky.social uses secp256k1 as Multikey, so it should not be a problem, but it is expected that authentication may fail and the feed may not be displayed.

For more information about authentication, please refer to AT Protocol's XRPC and Cryptography page.

Sequence Diagram

Registration

sequenceDiagram
    autonumber
    actor User
    participant Cloudflare as Cloudflare Worker
    participant Bluesky
    User->>Cloudflare: request /api/register
    Note left of Cloudflare: Handle Name and App Password
    Cloudflare->>Bluesky: XRPC createSession
    Note right of Cloudflare: Handle Name and App Password
    Bluesky->>Cloudflare: respond did and accessToken
    Cloudflare->>Bluesky: XRPC getProfiles
    Bluesky->>Cloudflare: respond user labels and viewer status
    Cloudflare->>Cloudflare: check for acceptance
    break if user is an unacceptable user
        Cloudflare->>User: respond forbidden
    end
    Cloudflare->>Cloudflare: cache public key
    Cloudflare->>User: respond token
    Note left of Cloudflare: Token

Bookmark

Add

sequenceDiagram
    autonumber
    actor User
    participant Cloudflare as Cloudflare Worker
    participant Bluesky
    User->>Cloudflare: request /api/bookmark
    Note left of Cloudflare: Post URL and Bearer token
    Cloudflare->>Cloudflare: verify token
    break verification failed
        Cloudflare->>User: respond unauthorized
    end
    Cloudflare->>Cloudflare: search Post uri from cache
    opt if not found
        Cloudflare->>Bluesky: XRPC describeRepo
        Note right of Cloudflare: Post URL
        Bluesky->>Cloudflare: respond Post uri
    end
    Cloudflare->>Cloudflare: save bookmark to DB
    Cloudflare->>User: respond Created

Delete

sequenceDiagram
    autonumber
    actor User
    participant Cloudflare as Cloudflare Worker
    participant Bluesky
    User->>Cloudflare: request /api/bookmark
    Note left of Cloudflare: Post URL and Bearer token
    Cloudflare->>Cloudflare: verify token
    break verification failed
        Cloudflare->>User: respond unauthorized
    end
    Cloudflare->>Cloudflare: search Post uri from cache
    opt if not found
        Cloudflare->>Bluesky: XRPC describeRepo
        Note right of Cloudflare: Post URL
        Bluesky->>Cloudflare: respond Post uri
    end
    Cloudflare->>Cloudflare: delete bookmark
    Cloudflare->>User: respond OK

Custom Feed

sequenceDiagram
    autonumber
    actor User
    participant Cloudflare as Cloudflare Worker
    participant Bluesky
    User->>Bluesky: request bookmark custom feed
    Bluesky->>Cloudflare: get bookmark custom feed
    Note right of Bluesky: Signed token
    Cloudflare->>Cloudflare: get public key from cache
    Cloudflare->>Cloudflare: verify token
    opt if verification failed, refresh public key
        Cloudflare->>Bluesky: XRPC describeRepo
        Bluesky->>Cloudflare: respond new public key
        Cloudflare->>Cloudflare: re-verify user
        Cloudflare->>Cloudflare: cache public key
    end
    Cloudflare->>Cloudflare: get posts from cache
    opt if not in cache
      Cloudflare->>Cloudflare: search posts from DB
    end
    Cloudflare->>Bluesky: respond list of bookmarked posts
    Bluesky->>User: respond feed of bookmark

License

Licensed under MIT