astro-atmosphere-comments 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +21 -23
  3. package/components/AtmosphereComments.astro +186 -83
  4. package/components/AtmosphereReactions.astro +86 -51
  5. package/components/SignInForm.astro +17 -4
  6. package/components/atmosphere.css +21 -0
  7. package/index.mjs +42 -32
  8. package/lib/client/account.ts +553 -0
  9. package/lib/client/composer.ts +7 -10
  10. package/lib/http.ts +28 -0
  11. package/lib/permissions.ts +108 -0
  12. package/lib/public-settings.ts +18 -0
  13. package/lib/thread.ts +36 -6
  14. package/lib/uris.ts +31 -0
  15. package/locales/de.json +20 -4
  16. package/locales/en.json +20 -4
  17. package/locales/es.json +20 -4
  18. package/locales/fr.json +20 -4
  19. package/locales/it.json +20 -4
  20. package/locales/ja.json +20 -4
  21. package/locales/nl.json +20 -4
  22. package/locales/pt.json +20 -4
  23. package/package.json +2 -7
  24. package/routes/callback.astro +42 -0
  25. package/routes/client-metadata.json.ts +3 -2
  26. package/routes/following.ts +28 -0
  27. package/routes/identity.ts +69 -0
  28. package/routes/media.ts +7 -10
  29. package/routes/post.ts +1 -1
  30. package/routes/profile.ts +20 -0
  31. package/routes/richtext.ts +30 -0
  32. package/routes/thread.ts +23 -3
  33. package/routes/typeahead.ts +7 -23
  34. package/virtual.d.ts +5 -0
  35. package/bin/cli.mjs +0 -31
  36. package/lib/oauth.ts +0 -110
  37. package/lib/reader.ts +0 -130
  38. package/lib/store.ts +0 -46
  39. package/lib/visitor.ts +0 -42
  40. package/routes/callback.ts +0 -30
  41. package/routes/comment.ts +0 -97
  42. package/routes/delete.ts +0 -41
  43. package/routes/follow.ts +0 -71
  44. package/routes/jwks.json.ts +0 -13
  45. package/routes/login.ts +0 -58
  46. package/routes/logout.ts +0 -20
  47. package/routes/me.ts +0 -12
  48. package/routes/react.ts +0 -48
  49. package/routes/state.ts +0 -24
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 (4 November 2026)
4
+
5
+ **Readers' sign-ins now stay in their own browsers.** This changes how the package works, so read "Upgrading" below.
6
+
7
+ - **Browser sign-in:** readers sign in as a browser ("public") OAuth client, with `@atproto/oauth-client-browser`. Their sign-in is kept in their browser's storage, with a key the browser won't let even the page's code read out, and comments, likes, reposts, deletes and follows go from their browser straight to their own account host. Your server never sees a sign-in, sets no cookies and stores nothing, so it has no way to post for anyone. Signing out cancels the sign-in at the reader's host and deletes it from their browser. Account hosts give browser sign-ins a shorter life (Bluesky's last about two weeks).
8
+ - **One approval for everything:** signing in asks for all that readers can do here at once (posting; liking and reposting; deleting their comments; following your accounts), plus hiding comments when the account is one of your own (`allowedAuthors`). Deleting and following were asked for separately before; with the sign-in in the browser, separate trips to the account host only confused readers. Permissions are grouped in `lib/permissions.ts`. Readers whose sign-in lacks a group (one from before it was added) are asked for it when it's first needed, after the page says what it's for, which account, and that their account host may ask them to sign in again first. `SignInForm` takes a `permissions` prop for anything more.
9
+ - **Hide comments:** signed in as the account that posted the announcement, a Hide button under other people's comments hides them here and on Bluesky (the announcement's threadgate, as the Bluesky app does). The first hide asks for that permission. Show them again in the Bluesky app. Fires `atmosphere:hidden` (`detail`: `uri`).
10
+ - **Respecting "logged-out" privacy:** accounts that use Bluesky's "Discourage apps from showing my account to logged-out users" are no longer shown on the page, which is public. Their comments become a short "Not shown here" placeholder (nothing about them reaches the page) with the replies under them kept, and they're left out of the like and repost faces. New wording key `comment.withheld`; style `.atmo-comment__withheld`.
11
+ - **Fewer third parties:** account lookups while signing in go through your server (`/atmosphere/identity`), so readers' browsers contact only their own account host.
12
+ - Under the sign-in button, a line says the sign-in stays in the browser, with a link to your privacy policy if you set `policyUri`.
13
+ - Routes: `login`, `logout`, `me`, `comment`, `react`, `delete`, `follow`, `state` and `jwks.json` are gone. New: `identity`, `profile`, `richtext` (links and mentions in a comment, before it's posted) and `following`; `callback` is now a page; `POST /atmosphere/thread` refreshes a thread after a reader posts. The `typeahead` limits are per address for everyone.
14
+ - The `keygen` command is gone: there's no key.
15
+ - New wording keys (`signIn.access`, `signIn.policy`, `composer.needsPermission`, `comment.hide…`, `comment.deleteElsewhere`, `permission.…`, `callback.signingIn`) in all eight languages; `messages.newPermissions` is gone and `messages.needsPermission` is now general.
16
+
17
+ ### Upgrading from 0.4
18
+
19
+ - Remove `ATMOSPHERE_PRIVATE_JWK` and `ATMOSPHERE_DATA_DIR` from your server's environment (and your deploy's secrets), and delete the data folder (default `.data/atmosphere`). The sign-ins in it can't be used without it: their tokens only work with keys kept in those files.
20
+ - **Readers will need to sign in again.** Their old sign-ins (made with the server key) may still be listed at their account host until they expire; they can remove them there.
21
+ - If you set a Content Security Policy, allow `connect-src` to readers' account hosts (any `https:` address), since the browser now talks to them directly.
22
+ - The site no longer needs a disk that persists.
23
+
24
+ ## 0.4.2 (4 November 2026)
25
+
26
+ - Links open in a new tab: those in the sign-in form, "Reply on Bluesky instead", the credit, and the thread's names, dates, faces and links in comments, so readers don't lose their place (and a half-written comment).
27
+
3
28
  ## 0.4.1 (4 November 2026)
4
29
 
5
30
  - Fixed: the sign-in and reply overlays of `AtmosphereReactions` showed at the bottom of the page instead of staying hidden until opened (the shared stylesheet made every `.atmo` element a block, dialogs included).
package/README.md CHANGED
@@ -10,6 +10,8 @@ Under each post, readers type their Bluesky handle (or that of any other [Atmosp
10
10
  - **@mention** people, with suggestions as they type;
11
11
  - **delete** their own comments.
12
12
 
13
+ And signed in as one of your own accounts, you can **hide** comments under your posts, here and on Bluesky.
14
+
13
15
  Everything they post is written to **their own account**, as ordinary Bluesky posts, likes and reposts: comments are replies to your post's announcement on Bluesky. They can delete them in any Bluesky app, and people who prefer the app can reply there instead; it all shows up on your page the same way.
14
16
 
15
17
  The component also **shows the thread**: who liked and reposted your post, and the comments, nested, with their own Reply, Like and Repost.
@@ -20,17 +22,17 @@ See it on [saltedlolly.com](https://saltedlolly.com/2005/07/13/summer-jam-party-
20
22
 
21
23
  1. You announce each post on Bluesky (a post linking to it). That post's thread is the comment section.
22
24
  2. A reader signs in with **AT Protocol OAuth, straight to their own account host** (Bluesky's, for most people), which asks them to approve your site, named by its address (example.com). There is no third-party login service in between, and your site never sees a password.
23
- 3. Your site asks only for what commenting needs: to create posts, to create and delete likes and reposts, and to see which posts the reader has already liked or reposted. Permission to delete posts is asked for separately, only the first time a reader deletes one of their comments on your site, and the site only ever uses it on their own comments under your posts. Permission to follow is asked for the same way, the first time a reader presses Follow, and only your own accounts can be followed through it. Readers can revoke it all at any time in their Bluesky settings.
24
- 4. Your server keeps each reader's sign-in (a random cookie in their browser; their tokens on your server), and posts on their behalf when they press a button.
25
+ 3. **The sign-in stays in the reader's browser.** Your server never sees it: comments, likes and reposts go from the reader's browser straight to their own account host. So your server has no way to post for anyone, a copy of your server's files holds nothing about your readers, and when a reader signs out, the sign-in is cancelled at their host and deleted from their browser. That's it: nothing is left anywhere.
26
+ 4. Your site asks, in one approval, only for what readers can do on it: to create posts, to like and repost, to delete their comments and to follow your accounts (and, when one of your own accounts signs in, to hide comments). Each is used only on your posts, the comments under them and your accounts. Readers can see and cancel it all at their account host.
25
27
 
26
28
  Readers who'd rather not sign in can use the **Reply on Bluesky instead** link under the form.
27
29
 
28
- Images (avatars, link-card pictures) and account searches (the suggestions under the sign-in box and for @mentions) go through your server, so readers' browsers never contact Bluesky directly.
30
+ Images (avatars, link-card pictures), account searches (the suggestions under the sign-in box and for @mentions) and account lookups when signing in go through your server, so readers' browsers contact only their own account host, never Bluesky's other servers.
29
31
 
30
32
  ## Requirements
31
33
 
32
34
  - **Astro 7** with an on-demand (server) adapter, such as [`@astrojs/node`](https://docs.astro.build/en/guides/integrations-guide/node/). The pages themselves can stay static; the integration adds a few server routes.
33
- - **Node 20.19 or later**, with a **disk that persists** between deploys and restarts: sign-ins are stored as files. (Serverless hosts without a lasting disk aren't supported yet.)
35
+ - **Node 20.19 or later.** Nothing is stored on your server (no sign-ins, no cookies, no files), so no disk or database is needed.
34
36
  - A **Bluesky account that announces your posts**. Each post needs its announcement's address, or a [Standard.site](https://standard.site) record that points to it (see below).
35
37
  - Your site's **`site`** set in `astro.config`.
36
38
 
@@ -64,17 +66,9 @@ export default defineConfig({
64
66
 
65
67
  To find an account's DID: `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=you.bsky.social`.
66
68
 
67
- ### 2. Create your site's signing key
68
-
69
- ```sh
70
- npx astro-atmosphere-comments keygen
71
- ```
72
-
73
- This adds `ATMOSPHERE_PRIVATE_JWK` to your `.env` without printing it. Your site signs its requests to readers' account hosts with it; the public half is published at `/atmosphere/jwks.json`, and the site's details for account hosts (its name, logo and permissions) at `/oauth-client-metadata.json`. That second file has to be at your site's root: it's the conventional address, and only then does Bluesky's approval page name your site by its address alone. Set the same variable on your server, keep `.env` out of Git, and keep a backup: **a new key signs every reader out.**
74
-
75
- On the server, also set `ATMOSPHERE_DATA_DIR` to a folder **outside your deploy folder** (for example `/var/lib/example-com/atmosphere`), so deploys don't sign readers out. It defaults to `.data/atmosphere` in the project.
69
+ The integration also publishes your site's details for account hosts (its name, logo and permissions) at `/oauth-client-metadata.json`. That file has to be at your site's root: it's the conventional address, and only then does Bluesky's approval page name your site by its address alone. There's no key or secret to set up.
76
70
 
77
- ### 3. Put the component under your posts
71
+ ### 2. Put the component under your posts
78
72
 
79
73
  ```astro
80
74
  ---
@@ -86,7 +80,7 @@ import AtmosphereComments from "astro-atmosphere-comments/AtmosphereComments.ast
86
80
 
87
81
  `post` can be the announcement's **bsky.app address** or its **AT URI**. Leave it out and the component uses the page's **Standard.site** record instead: if your page has `<link rel="site.standard.document" href="at://…">` and that record's `bskyPostRef` points to the announcement (as Standard.site publishers do), it just works. With no announcement to respond to, the component shows nothing (apart from your slot content).
88
82
 
89
- ### 4. Optional: likes and reposts on your other Bluesky posts
83
+ ### 3. Optional: likes and reposts on your other Bluesky posts
90
84
 
91
85
  If your pages show your Bluesky posts elsewhere (a "latest posts" feed on your homepage, say), readers can like and repost them there too. Put `AtmosphereReactions` once in your layout, and mark each post's element:
92
86
 
@@ -109,7 +103,7 @@ import AtmosphereReactions from "astro-atmosphere-comments/AtmosphereReactions.a
109
103
 
110
104
  Each marked post gets a row of icons: Reply, Repost, Quote and Like, with counts for Repost and Like. Readers already signed in on your site like and repost straight away, and Reply and Quote open the comment box in an overlay (with the quote preview); what they post goes to Bluesky, not your page, and the overlay says so with a link to it. Anyone else is offered the sign-in form in an overlay when they press Like or Repost (their like or repost is done when they come back), and Reply and Quote open the post on Bluesky. It works with posts from loaders such as [`@ascorbic/bluesky-loader`](https://www.npmjs.com/package/@ascorbic/bluesky-loader) or embeds such as [`@astro-community/astro-embed-bluesky`](https://www.npmjs.com/package/@astro-community/astro-embed-bluesky) (wrap the embed in the marked element). Only posts by your `allowedAuthors` can be liked or reposted through your site.
111
105
 
112
- A "follow me" link can become a **Follow** button for signed-in readers: mark it with the account's DID and handle. It shows "Following" once they do (a second press unfollows, as on Bluesky). The first follow asks the reader's account host for one more permission. Only your `allowedAuthors` can be followed through your site, and the account's owner (who can't follow themselves) keeps the plain link.
106
+ A "follow me" link can become a **Follow** button for signed-in readers: mark it with the account's DID and handle. It shows "Following" once they do (a second press unfollows, as on Bluesky). Only your `allowedAuthors` can be followed through your site, and the account's owner (who can't follow themselves) keeps the plain link.
113
107
 
114
108
  ```astro
115
109
  <a href="https://bsky.app/profile/you.bsky.social"
@@ -130,7 +124,7 @@ Run the dev server on `127.0.0.1` and open the site there, not at `localhost`:
130
124
  npx astro dev --host 127.0.0.1
131
125
  ```
132
126
 
133
- Locally the integration uses the protocol's development mode, so no key is needed and it works without a public address. Bluesky's approval screen will say a development app is asking; that's expected.
127
+ Locally the integration uses the protocol's development mode, so it works without a public address. Bluesky's approval screen will say a development app is asking, and sign-ins last only about a day; that's expected.
134
128
 
135
129
  ## Options
136
130
 
@@ -139,7 +133,7 @@ Locally the integration uses the protocol's development mode, so no key is neede
139
133
  | `clientName` | yes | Your site's name, given to readers' account hosts. (Bluesky's approval page shows your site's address instead, unless Bluesky has marked your site as trusted.) |
140
134
  | `allowedAuthors` | yes | DIDs of the accounts whose posts can be commented on through your site. Comments from these accounts get the Author badge. |
141
135
  | `logoUri` | | A square logo for the sign-in screen (absolute `https://` address). |
142
- | `policyUri`, `tosUri` | | Your privacy policy and terms, shown on the sign-in screen. |
136
+ | `policyUri`, `tosUri` | | Your privacy policy and terms, shown on the sign-in screen. The privacy policy is also linked under the sign-in form. |
143
137
  | `hidden` | | Comments never to show: bsky.app post or profile addresses, AT URIs, handles or DIDs. Replies you hide in the Bluesky app, and posts with Bluesky's moderation labels (spam and so on), are left out anyway. |
144
138
  | `base` | | Where the integration's routes live. Default `/atmosphere`. |
145
139
  | `locale` | | The language of the wording, such as `"fr"` or `"pt-BR"` (see Languages). |
@@ -207,17 +201,21 @@ The component fires (bubbling):
207
201
  - `atmosphere:commented` after a comment, reply or quote is posted (`detail`: `uri`, `url`, `text`, `author`, `createdAt`, `quote`, `parent`);
208
202
  - `atmosphere:reacted` after a like or repost of the post, or undoing one (`detail`: `kind`, `on`, `author`).
209
203
  - `atmosphere:deleted` after a reader deletes one of their comments (`detail`: `uri`).
204
+ - `atmosphere:hidden` after the announcement's author hides a comment (`detail`: `uri`).
210
205
 
211
206
  ## Security and privacy
212
207
 
213
- - **What your server stores:** for each signed-in reader, their DID and their OAuth tokens (which can only post, like and repost as described above, and delete posts if the reader allowed it), plus a random cookie value. Files are readable only by your site's user. Mention this in your privacy policy.
214
- - **What it refuses:** requests that change something must come from your own pages (checked by `Origin`); only your announcements and comments in their threads can be acted on; only a reader's own replies under your announcements can be deleted; comments, likes and searches are rate-limited per account, and searches by readers who aren't signed in per address and overall; the image route only passes on avatars and link pictures it has just shown.
215
- - **Moderation:** comments appear automatically. Hide one in the Bluesky app (it disappears here too), block the account, or add it to `hidden`.
216
- - Comments are plain text, as on Bluesky: links, @mentions and #hashtags become links.
208
+ - **Readers' sign-ins stay in their browsers.** The OAuth library (`@atproto/oauth-client-browser`) keeps the tokens in the browser's storage, together with a key the browser makes and won't let even the page's own code read out; the tokens only work with that key. Your server never receives them, sets no cookies and stores nothing about readers. Signing out cancels the sign-in at the reader's account host and deletes it from the browser.
209
+ - **What that does and doesn't promise.** Your server can't act for anyone, ever. While a reader has your page open, though, your page's code can use their sign-in, as with any web app they sign in to, so the code that runs matters: it's this package's open source code, and comments are only ever shown as plain text (links, @mentions and #hashtags become links; nothing is rendered as HTML). A [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) on your site adds a second line of defence.
210
+ - **What's checked before anything is sent** (by the browser code, `lib/client/account.ts`): only your announcements and the comments under them can be replied to, liked, reposted or quoted; only a reader's own replies under your announcements can be deleted; only your `allowedAuthors` can be followed; only an announcement's author can hide comments under it.
211
+ - **What your server does:** it shows the threads, looks up accounts and suggestions, finds the links and mentions in a comment before it's posted, and passes on avatars and link pictures it has just shown. None of it needs to know who a reader is. Each route that calls Bluesky is rate-limited per address, with an overall cap; requests that aren't simple reads must come from your own pages (checked by `Origin`). Readers' addresses are used only for those limits, never stored.
212
+ - **Trade-off:** account hosts trust browser sign-ins less than ones kept on a server, so they last less long (Bluesky's currently last about two weeks), and there's no way for a site to sign everyone out at once. For a reader who'd rather not trust your server, that's the point.
213
+ - **People who'd rather not be shown publicly:** Bluesky's setting "Discourage apps from showing my account to logged-out users" is respected, as your page is public. A comment from such an account shows only as "Not shown here…" (no name, picture or words reach the page), the replies under it stay, and the account is left out of the faces of who liked and reposted.
214
+ - **Moderation:** comments appear automatically. Hide one with Hide under it (signed in as the account that posted the announcement) or in the Bluesky app; it disappears here too. Show it again in the Bluesky app. You can also block the account, or add it to `hidden`.
217
215
 
218
216
  ## Limitations (for now)
219
217
 
220
- - Needs a Node server with a lasting disk (no serverless or edge hosts yet).
218
+ - Needs an on-demand (server) adapter, for the few routes above. Serverless hosts should work, as nothing is stored, but haven't been tried.
221
219
  - Rate limits are kept in memory, so they apply per server process.
222
220
  - Quoting individual comments isn't supported.
223
221