Bookmark feed for Bluesky, a serverless application running on Cloudflare Workers®︎, using hono and written in simple code.
- 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
- Get a token from the top page
- Install iOS shortcut
- Bookmark Bluesky post from share menu
- Refresh bookmark feed
- Get a token from the top page
- Install PWA
- Bookmark Bluesky post from share menu
- Refresh bookmark feed
- 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.
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).
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.
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.
Click Deploy button to create Cloudflare Workers project.
wrangler login
on your PC.- Clone your forked repository.
- 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"
- Copy a line
database_id
in output and replace database_id in wrangler.toml - 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" }
- Copy a value of
id
in output and replace id in wrangler.toml - Initialize D1 database table with
wrangler d1 execute bluebookmark --file=drizzle/0000_productive_riptide.sql
- Commit changes of wranger.toml file with
git commit
Push your changes to GitHub and it will be automatically deployed by GitHub Actions. You can also run pnpm run deploy
locally.
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 |
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.
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
.
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.
- 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.
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.
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
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
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
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
Licensed under MIT