diff --git a/README.md b/README.md index 7c01942..bcee756 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# notify.atmo.tools +# atmo.pub Notifications for the AT Protocol. Any atproto app can ask a user for permission to notify them; the user approves which apps may notify them in a web dashboard, and deliveries go out (v1) through a Telegram bot. -- **Dashboard** — sign in, approve apps, link Telegram: https://notify.atmo.tools -- **Developer docs**: https://notify.atmo.tools/docs -- **Live example sender**: https://example.notify.atmo.tools · [source](apps/example-sender) -- **Relay API + `did:web`**: https://notifs.atmo.tools +- **Dashboard** — sign in, approve apps, link Telegram: https://atmo.pub +- **Developer docs**: https://atmo.pub/docs +- **Live example sender**: https://example.atmo.pub · [source](apps/example-sender) +- **Relay API + `did:web`**: https://relay.atmo.pub -> `notify.atmo.tools` is the dashboard; `notifs.atmo.tools` (with an "s") is the -> relay XRPC API. User-facing links point at `notify.atmo.tools`. +> `atmo.pub` is the dashboard; `relay.atmo.pub` (with an "s") is the +> relay XRPC API. User-facing links point at `atmo.pub`. ## Sending notifications from your app @@ -21,19 +21,19 @@ Two endpoints, two auth mechanisms: - **`send`** — authenticated by **your app's own DID key**. Delivers to a user who has granted you permission. -Full walkthrough with code is at **https://notify.atmo.tools/docs**, and +Full walkthrough with code is at **https://atmo.pub/docs**, and [`apps/example-sender`](apps/example-sender) is a complete, ~300-line reference -implementation of both flows (live at https://example.notify.atmo.tools). +implementation of both flows (live at https://example.atmo.pub). ## This repo -- **`apps/relay`** — the Cloudflare Worker at `notifs.atmo.tools`. Pure XRPC: it +- **`apps/relay`** — the Cloudflare Worker at `relay.atmo.pub`. Pure XRPC: it verifies inbound service-auth JWTs, stores grants / pending requests / channels in D1, rate-limits with KV, and dispatches deliveries through a Cloudflare Queue. No firehose, no repo records, no signing. -- **`apps/web`** — the SvelteKit dashboard at `notify.atmo.tools` (+ `/docs`). -- **`apps/example-sender`** — one-page sender demo at `example.notify.atmo.tools`. -- **`packages/lexicons`** — the shared `tools.atmo.notifs.*` lexicons (13) and +- **`apps/web`** — the SvelteKit dashboard at `atmo.pub` (+ `/docs`). +- **`apps/example-sender`** — one-page sender demo at `example.atmo.pub`. +- **`packages/lexicons`** — the shared `pub.atmo.notify.*` lexicons (13) and generated types. Running, configuring, and deploying everything: **[DEVELOPMENT.md](DEVELOPMENT.md)**. diff --git a/REDESIGN.md b/REDESIGN.md new file mode 100644 index 0000000..42a1514 --- /dev/null +++ b/REDESIGN.md @@ -0,0 +1,108 @@ +# atmo.pub redesign — implementation plan + +Redesign of the webapp (`apps/web`) into a v2-style installable **PWA** with +**web push**, an **inbox**, and **per-category routing**. Loose inspiration from +`ref/design/project/v2.*` (read `v2.html` → its imports for screen-level detail). +Backend work lands in the relay (`apps/relay`). + +## Locked decisions (2026-05-23) + +1. **Domains unchanged.** Relay stays `relay.atmo.pub` (`did:web` untouched), + webapp stays `atmo.pub`. This is a **UI rebrand only** → product name + becomes **atmo.pub**. No OAuth `ORIGIN`/client_id change. +2. **Categories = free-form.** `send` carries an optional `category` string + + optional `categoryDescription`. No sender "declare" step; the relay learns + categories as they arrive. Routing keys on `(recipient, sender, category)`. +3. **No email channel.** Channels are **push + telegram** only. +4. **Trusted apps, hardcoded.** Replaces v2's "follows"/privacy/auto-allow: a + hardcoded list of sender DIDs in relay config that **auto-grant** at + `requestPermission` (skip pending). No Bluesky graph lookups, no privacy modes. +5. **Inbox keeps everything.** Every delivered notification is stored in the + inbox (full history); per-category routing only decides which *alert* channels + also fire (push / telegram). No pruning for now (revisit retention later). +6. **Keep the bell logo.** Wordmark renders `atmo.pub` (`.pub` muted), bell in accent. + +Reused as-is: the v2 color tokens are identical to `apps/web/src/routes/layout.css`, +so theming carries straight over. + +## Routing model + +Routes available (after cutting email): `push`, `telegram`, `push+telegram`, +`inbox` (inbox-only, no alert), `off` (alias of inbox-only for now), `default` +(inherit user-wide `defaultRoute`). Resolution per notification: + +1. Always insert a row into `notifications` (inbox = canonical log). +2. Look up routing for `(recipient, sender, category)`; fall back to the user's + `defaultRoute`. +3. Fan out alerts to the resolved channels (push and/or telegram). + +## Phases + +### Phase 0 — Rebrand + foundation (no behavior change) +- Rename product → **atmo.pub** in `apps/web`, `apps/homepage`, relay Telegram + copy (`telegram/commands.ts`, dashboard links), and docs. Wordmark `atmo.pub`, + keep bell `Logomark`. +- Build a shared component layer in `apps/web/src/lib/components` ported from v2: + `Icon` (glyph set), `AppMark`, `RouteChip`, `IOSToggle`, `ActorStack`, plus the + Inbox/Apps/Settings shell (desktop split + mobile bottom-tab nav). +- Remove the migrated landing (`/`) and `/docs` from `apps/web` (now in + `apps/homepage`); `/` becomes the clean Login. + +### Phase 1 — PWA shell + restyle current features (frontend only) +- **Login** (clean), **Apps** (today's grants + pending + denied, v2 styling), + **Settings** (Telegram channel + existing toggle). **Inbox** tab present as a + "coming soon" placeholder. Responsive desktop + mobile. +- Make `apps/web` an **installable PWA**: `manifest.webmanifest`, icons, minimal + service worker (install/offline shell only — no push yet). +- Backend: none (rides existing `RelayRpc` binding). + +### Phase 2 — Web push (headline feature) +- Relay: + - Generate **VAPID** keypair (public in config, private as `VAPID_PRIVATE_KEY` + secret). + - New `push_subscriptions` table: `(did, endpoint PK, p256dh, auth, created_at)` + — multiple per user. Telegram stays in `channels`. Migration `0004_push`. + - Web Push delivery (aes128gcm encryption + VAPID JWT via WebCrypto, no node) in + the dispatcher; dead endpoints (404/410) pruned. + - Binding methods `registerWebPush(did, subscription)` / `unregisterWebPush(did, endpoint)`. + - Until Phase 4, `send` fans out globally to push + telegram. +- Client: service worker `push` → `showNotification`; `PushManager.subscribe` with + the VAPID public key; subscription registered via web server → binding. Settings + shows push devices (count + revoke). + +### Phase 3 — Inbox +- Relay: + - `notifications` table: `(id, recipient_did, sender_did, category, title, body, + uri, actors_json, created_at, read_at)`. Migration `0005_inbox`. + - `send` writes a notification row for every delivery; gains optional + **`category`**, **`categoryDescription`**, **`actors[]`** (additive lexicon change). + - Binding methods `listNotifications(did, cursor)` (paged) + `markRead(did, ids|all)`. +- Client: **Inbox** screen — actor stacks, route chips, unread state, mark-read; + swipe/grouped variants optional. +- Update `apps/example-sender` + homepage `/docs` for the new `send` fields. + +### Phase 4 — Per-category routing + trusted apps +- Relay: + - `app_categories` table: discovered `(sender_did, category, description, count, + last_seen)` so the routing UI can list them. + - `routing` table: `(recipient_did, sender_did, category, routes_json)`; user + `default_route` + optional `quiet_hours` columns on `users`. Migration `0006_routing`. + - Dispatcher resolves route per notification (see Routing model) and fans out. + - **Trusted apps**: hardcoded `TRUSTED_SENDERS` in relay config; `requestPermission` + auto-grants for those DIDs. + - (Optional) quiet hours applied at delivery; can be cut. + - Binding methods `getRouting(did)` / `setRouting(did, sender, category, routes)`; + extend `updateSettings` for `defaultRoute` (+ quiet hours). +- Client: app-detail per-category routing UI, Default-routing screen, Channels, + read-only Trusted-apps list. (v2 has 3 routing-UI variations in + `v2-variations.jsx` — pick one.) + +## Deferred / out of scope (for now) +- Email channel. Bluesky-graph "follows" privacy. Notification retention/pruning. +- Native push (project is PWA-only — see `mobile-and-push` memory). + +## Cross-cutting +- Theming reuse (tokens already match v2); accessibility (focus rings, reduced + motion already in `layout.css`); keep the `RelayRpc` service-binding boundary — + all new first-party methods go on the binding, never public XRPC. Only `send` + changes are public (and additive). diff --git a/apps/example-sender/README.md b/apps/example-sender/README.md index 311d939..5f58e0b 100644 --- a/apps/example-sender/README.md +++ b/apps/example-sender/README.md @@ -1,7 +1,7 @@ # Atmo Notifs — example sender A tiny reference app showing how any AT Protocol app sends notifications through -[Atmo Notifs](https://notifs.atmo.tools). One page, two auth mechanisms. +[Atmo Notifs](https://relay.atmo.pub). One page, two auth mechanisms. ## The two auth mechanisms diff --git a/apps/example-sender/src/lib/config.ts b/apps/example-sender/src/lib/config.ts index 219e424..fae4c34 100644 --- a/apps/example-sender/src/lib/config.ts +++ b/apps/example-sender/src/lib/config.ts @@ -2,33 +2,33 @@ import { dev } from '$app/environment'; /** This app's public domain. */ -export const APP_DOMAIN = 'example.notify.atmo.tools'; +export const APP_DOMAIN = 'example.atmo.pub'; /** This app's sender DID — what the user approves and what `send` authenticates as. */ export const SENDER_DID = `did:web:${APP_DOMAIN}`; /** Display name + description shown to the user when requesting permission. */ export const APP_TITLE = 'Example Sender'; -export const APP_DESCRIPTION = 'Demo of how to integrate with notify.atmo.tools.'; +export const APP_DESCRIPTION = 'Demo of how to integrate with atmo.pub.'; /** * The relay's XRPC API. In dev (`vite dev`) talk to the local relay running via * `wrangler dev`; in production use the deployed relay. The DID (service-auth * `aud`) is the same either way — the local relay's `RELAY_DID` var matches. */ -export const RELAY_ORIGIN = dev ? 'http://localhost:8787' : 'https://notifs.atmo.tools'; -export const RELAY_DID = 'did:web:notifs.atmo.tools'; +export const RELAY_ORIGIN = dev ? 'http://localhost:8787' : 'https://relay.atmo.pub'; +export const RELAY_DID = 'did:web:relay.atmo.pub'; /** The web dashboard where users approve apps + link Telegram (note: no "s"). */ -export const DASHBOARD_ORIGIN = 'https://notify.atmo.tools'; +export const DASHBOARD_ORIGIN = 'https://atmo.pub'; /** * OAuth scope: the sender app only needs `requestPermission` via the user's * session (`send` uses this app's own DID key, not the user's OAuth). Requested * as an individual `rpc` scope. */ -export const OAUTH_SCOPE = 'atproto rpc?lxm=tools.atmo.notifs.requestPermission&aud=*'; +export const OAUTH_SCOPE = 'atproto rpc?lxm=pub.atmo.notify.requestPermission&aud=*'; /** Branding. */ -export const PROJECT_NAME = 'notify.atmo.tools · example sender'; +export const PROJECT_NAME = 'atmo.pub · example sender'; export const GITHUB_URL = 'https://github.com/flo-bit/atproto-notify'; diff --git a/apps/example-sender/src/lib/server/relay.ts b/apps/example-sender/src/lib/server/relay.ts index 7afc7b2..2076b6e 100644 --- a/apps/example-sender/src/lib/server/relay.ts +++ b/apps/example-sender/src/lib/server/relay.ts @@ -41,7 +41,7 @@ async function postRelay(jwt: string, lxm: string, body: object): Promise ): Promise<{ id: string; status: 'pending' | 'alreadyGranted' }> { - const lxm = 'tools.atmo.notifs.requestPermission'; + const lxm = 'pub.atmo.notify.requestPermission'; const auth = await client.get('com.atproto.server.getServiceAuth', { params: { aud: RELAY_DID as Did, lxm: lxm as Nsid } }); @@ -65,7 +65,7 @@ export async function sendAsSender(input: { body: string; uri?: string; }): Promise<{ id: string; delivered: number }> { - const lxm = 'tools.atmo.notifs.send'; + const lxm = 'pub.atmo.notify.send'; const jwt = await mintSenderJwt(lxm); return postRelay(jwt, lxm, input) as Promise<{ id: string; delivered: number }>; } diff --git a/apps/example-sender/src/routes/+page.svelte b/apps/example-sender/src/routes/+page.svelte index 8399597..b8fcae0 100644 --- a/apps/example-sender/src/routes/+page.svelte +++ b/apps/example-sender/src/routes/+page.svelte @@ -93,7 +93,7 @@

{PROJECT_NAME}

A demo app showing how any AT Protocol app can send notifications through - notify.atmo.tools. The code is open source — it wires up both auth mechanisms (user-OAuth + atmo.pub. The code is open source — it wires up both auth mechanisms (user-OAuth for requesting permission, sender-DID for sending) in ~300 lines.

@@ -154,7 +154,7 @@ class="text-accent hover:underline" href={DASHBOARD_ORIGIN} target="_blank" - rel="noreferrer">notify.atmo.toolsatmo.pub, where you can also connect Telegram. Already approved? Skip to Step 2.

{/if} @@ -186,14 +186,14 @@

⚠ Sent, but you have no delivery channels linked. Connect Telegram at notify.atmo.toolsatmo.pub.

{:else}

⚠ Approve this app first at notify.atmo.toolsatmo.pub, then try again.

{/if} @@ -207,7 +207,7 @@ {GITHUB_URL}. Powered by notify.atmo.toolsatmo.pub. diff --git a/apps/example-sender/wrangler.jsonc b/apps/example-sender/wrangler.jsonc index db9894e..4ad69b0 100644 --- a/apps/example-sender/wrangler.jsonc +++ b/apps/example-sender/wrangler.jsonc @@ -11,7 +11,7 @@ // Public origin (NOT secret) — must equal the served domain (OAuth client_id derives from it). "vars": { - "ORIGIN": "https://example.notify.atmo.tools" + "ORIGIN": "https://example.atmo.pub" }, // KV namespaces for @svelte-atproto/oauth session/state storage: @@ -28,5 +28,5 @@ // SENDER_PRIVATE_KEY (pnpm sender:keygen → the private multikey; for `send`) // // Custom domain: bind it in the CF dashboard, or add: - // "routes": [{ "pattern": "example.notify.atmo.tools", "custom_domain": true }] + // "routes": [{ "pattern": "example.atmo.pub", "custom_domain": true }] } diff --git a/apps/homepage/src/lib/config.ts b/apps/homepage/src/lib/config.ts index b3414d0..afce851 100644 --- a/apps/homepage/src/lib/config.ts +++ b/apps/homepage/src/lib/config.ts @@ -5,18 +5,18 @@ export const PROJECT_NAME = 'atmo.pub'; /** The webapp (PWA) — where the header/landing "Sign in" buttons send users. */ -export const WEBAPP_URL = 'https://notify.atmo.tools'; +export const WEBAPP_URL = 'https://atmo.pub'; /** GitHub repo link in the footer. */ export const GITHUB_URL = 'https://github.com/flo-bit/atproto-notify'; // --- Relay (used only by the /docs code examples) -------------------------- -/** `https://notifs.atmo.tools` — base for the relay XRPC examples. */ -export const RELAY_ORIGIN = 'https://notifs.atmo.tools'; +/** `https://relay.atmo.pub` — base for the relay XRPC examples. */ +export const RELAY_ORIGIN = 'https://relay.atmo.pub'; /** The relay's `did:web` identity (the `aud` for service-auth JWTs in examples). */ -export const RELAY_DID = 'did:web:notifs.atmo.tools'; +export const RELAY_DID = 'did:web:relay.atmo.pub'; /** Lexicon NSID prefix for relay methods. */ -export const LEXICON_PREFIX = 'tools.atmo.notifs'; +export const LEXICON_PREFIX = 'pub.atmo.notify'; diff --git a/apps/homepage/src/routes/docs/+page.svelte b/apps/homepage/src/routes/docs/+page.svelte index b1429b5..7d41035 100644 --- a/apps/homepage/src/routes/docs/+page.svelte +++ b/apps/homepage/src/routes/docs/+page.svelte @@ -60,9 +60,9 @@ both flows is live at example.notify.atmo.toolsexample.atmo.pub — try it, then read the

- atproto rpc?lxm=tools.atmo.notifs.requestPermission&aud=* + atproto rpc?lxm=pub.atmo.notify.requestPermission&aud=*

Then mint a service-auth JWT on the user's PDS via diff --git a/apps/relay/src/env.ts b/apps/relay/src/env.ts index 95f35e2..aa7f5a9 100644 --- a/apps/relay/src/env.ts +++ b/apps/relay/src/env.ts @@ -51,7 +51,7 @@ export interface Env { /** Queue producer for async delivery. */ DISPATCH_QUEUE: Queue; - /** `did:web:notifs.atmo.tools` — this relay's DID (var). */ + /** `did:web:relay.atmo.pub` — this relay's DID (var). */ RELAY_DID: string; /** Telegram bot username, used to build deep links (var). */ BOT_USERNAME: string; diff --git a/apps/relay/src/lib/trusted.ts b/apps/relay/src/lib/trusted.ts index c9a4998..0f0f99e 100644 --- a/apps/relay/src/lib/trusted.ts +++ b/apps/relay/src/lib/trusted.ts @@ -3,7 +3,7 @@ import type { Did } from '@atcute/lexicons'; // "Trusted apps": sender DIDs that are auto-granted at `requestPermission` // (skipping the pending step). Hardcoded for now — add known-good app DIDs here. export const TRUSTED_SENDERS: readonly Did[] = [ - // 'did:web:example.notify.atmo.tools', + // 'did:web:example.atmo.pub', ]; export function isTrustedSender(did: Did): boolean { diff --git a/apps/relay/src/router.ts b/apps/relay/src/router.ts index 654f787..b19da85 100644 --- a/apps/relay/src/router.ts +++ b/apps/relay/src/router.ts @@ -1,4 +1,4 @@ -import { ToolsAtmoNotifsRequestPermission, ToolsAtmoNotifsSend } from '@atmo/notifs-lexicons'; +import { PubAtmoNotifyRequestPermission, PubAtmoNotifySend } from '@atmo/notifs-lexicons'; import { XRPCRouter } from '@atcute/xrpc-server'; import { getVerifier } from './auth/verifier'; @@ -30,8 +30,8 @@ export function buildRouter(env: Env, ctx: ExecutionContext): XRPCRouter { }, }); - router.addProcedure(ToolsAtmoNotifsRequestPermission.mainSchema, makeRequestPermission(app)); - router.addProcedure(ToolsAtmoNotifsSend.mainSchema, makeSend(app)); + router.addProcedure(PubAtmoNotifyRequestPermission.mainSchema, makeRequestPermission(app)); + router.addProcedure(PubAtmoNotifySend.mainSchema, makeSend(app)); return router; } diff --git a/apps/relay/src/rpc/entrypoint.ts b/apps/relay/src/rpc/entrypoint.ts index b5dee3b..697b95e 100644 --- a/apps/relay/src/rpc/entrypoint.ts +++ b/apps/relay/src/rpc/entrypoint.ts @@ -11,17 +11,17 @@ import type { NotifsRpc, PushSubscriptionInput, RoutingConfig, - ToolsAtmoNotifsDenyPending, - ToolsAtmoNotifsGetSettings, - ToolsAtmoNotifsGrant, - ToolsAtmoNotifsLinkChannel, - ToolsAtmoNotifsListChannels, - ToolsAtmoNotifsListGrants, - ToolsAtmoNotifsListPending, - ToolsAtmoNotifsMuteGrant, - ToolsAtmoNotifsRevoke, - ToolsAtmoNotifsUnlinkChannel, - ToolsAtmoNotifsUpdateSettings, + PubAtmoNotifyDenyPending, + PubAtmoNotifyGetSettings, + PubAtmoNotifyGrant, + PubAtmoNotifyLinkChannel, + PubAtmoNotifyListChannels, + PubAtmoNotifyListGrants, + PubAtmoNotifyListPending, + PubAtmoNotifyMuteGrant, + PubAtmoNotifyRevoke, + PubAtmoNotifyUnlinkChannel, + PubAtmoNotifyUpdateSettings, } from '@atmo/notifs-lexicons'; import type { Env } from '../env'; @@ -38,37 +38,37 @@ import * as ops from './ops'; * the shared contract the web app types its binding against. */ export class RelayRpc extends WorkerEntrypoint implements NotifsRpc { - grant(did: Did, input: ToolsAtmoNotifsGrant.$input) { + grant(did: Did, input: PubAtmoNotifyGrant.$input) { return ops.grant(this.env, did, input); } - revoke(did: Did, input: ToolsAtmoNotifsRevoke.$input) { + revoke(did: Did, input: PubAtmoNotifyRevoke.$input) { return ops.revoke(this.env, did, input); } - denyPending(did: Did, input: ToolsAtmoNotifsDenyPending.$input) { + denyPending(did: Did, input: PubAtmoNotifyDenyPending.$input) { return ops.denyPending(this.env, did, input); } - muteGrant(did: Did, input: ToolsAtmoNotifsMuteGrant.$input) { + muteGrant(did: Did, input: PubAtmoNotifyMuteGrant.$input) { return ops.muteGrant(this.env, did, input); } - linkChannel(did: Did, input: ToolsAtmoNotifsLinkChannel.$input) { + linkChannel(did: Did, input: PubAtmoNotifyLinkChannel.$input) { return ops.linkChannel(this.env, did, input); } - unlinkChannel(did: Did, input: ToolsAtmoNotifsUnlinkChannel.$input) { + unlinkChannel(did: Did, input: PubAtmoNotifyUnlinkChannel.$input) { return ops.unlinkChannel(this.env, did, input); } - updateSettings(did: Did, input: ToolsAtmoNotifsUpdateSettings.$input) { + updateSettings(did: Did, input: PubAtmoNotifyUpdateSettings.$input) { return ops.updateSettings(this.env, did, input); } - listGrants(did: Did): Promise { + listGrants(did: Did): Promise { return ops.listGrants(this.env, did); } - listPending(did: Did): Promise { + listPending(did: Did): Promise { return ops.listPending(this.env, did); } - listChannels(did: Did): Promise { + listChannels(did: Did): Promise { return ops.listChannels(this.env, did); } - getSettings(did: Did): Promise { + getSettings(did: Did): Promise { return ops.getSettings(this.env, did); } registerWebPush(did: Did, sub: PushSubscriptionInput) { diff --git a/apps/relay/src/rpc/ops.ts b/apps/relay/src/rpc/ops.ts index 566f229..6c697bf 100644 --- a/apps/relay/src/rpc/ops.ts +++ b/apps/relay/src/rpc/ops.ts @@ -16,17 +16,17 @@ import type { PushSubscriptionInput, RoutingApp, RoutingConfig, - ToolsAtmoNotifsDenyPending, - ToolsAtmoNotifsGetSettings, - ToolsAtmoNotifsGrant, - ToolsAtmoNotifsLinkChannel, - ToolsAtmoNotifsListChannels, - ToolsAtmoNotifsListGrants, - ToolsAtmoNotifsListPending, - ToolsAtmoNotifsMuteGrant, - ToolsAtmoNotifsRevoke, - ToolsAtmoNotifsUnlinkChannel, - ToolsAtmoNotifsUpdateSettings, + PubAtmoNotifyDenyPending, + PubAtmoNotifyGetSettings, + PubAtmoNotifyGrant, + PubAtmoNotifyLinkChannel, + PubAtmoNotifyListChannels, + PubAtmoNotifyListGrants, + PubAtmoNotifyListPending, + PubAtmoNotifyMuteGrant, + PubAtmoNotifyRevoke, + PubAtmoNotifyUnlinkChannel, + PubAtmoNotifyUpdateSettings, } from '@atmo/notifs-lexicons'; import * as q from '../db/queries'; @@ -37,8 +37,8 @@ import { addMinutes, now, toIsoDatetime } from '../lib/time'; export async function grant( env: Env, did: Did, - input: ToolsAtmoNotifsGrant.$input, -): Promise { + input: PubAtmoNotifyGrant.$input, +): Promise { await q.ensureUser(env.DB, did, now()); // When granting from a pending request, copy its display metadata onto the @@ -67,8 +67,8 @@ export async function grant( export async function revoke( env: Env, did: Did, - input: ToolsAtmoNotifsRevoke.$input, -): Promise { + input: PubAtmoNotifyRevoke.$input, +): Promise { const revoked = await q.deleteGrant(env.DB, did, input.sender); // The pending request (if any) for this pair is now irrelevant. await q.deletePendingByPair(env.DB, did, input.sender); @@ -79,8 +79,8 @@ export async function revoke( export async function denyPending( env: Env, did: Did, - input: ToolsAtmoNotifsDenyPending.$input, -): Promise { + input: PubAtmoNotifyDenyPending.$input, +): Promise { // Delete the pending request without granting. Does not blocklist the sender; // they may request again once rate limits allow. const denied = await q.deletePendingById(env.DB, input.requestId, did); @@ -91,8 +91,8 @@ export async function denyPending( export async function muteGrant( env: Env, did: Did, - input: ToolsAtmoNotifsMuteGrant.$input, -): Promise { + input: PubAtmoNotifyMuteGrant.$input, +): Promise { await q.setGrantMuted(env.DB, did, input.sender, input.muted); return { muted: input.muted }; @@ -101,8 +101,8 @@ export async function muteGrant( export async function linkChannel( env: Env, did: Did, - input: ToolsAtmoNotifsLinkChannel.$input, -): Promise { + input: PubAtmoNotifyLinkChannel.$input, +): Promise { await q.ensureUser(env.DB, did, now()); const token = newLinkToken(); await q.insertLinkToken(env.DB, { @@ -119,8 +119,8 @@ export async function linkChannel( export async function unlinkChannel( env: Env, did: Did, - input: ToolsAtmoNotifsUnlinkChannel.$input, -): Promise { + input: PubAtmoNotifyUnlinkChannel.$input, +): Promise { const unlinked = await q.deleteChannel(env.DB, did, input.platform); return { unlinked }; @@ -129,8 +129,8 @@ export async function unlinkChannel( export async function updateSettings( env: Env, did: Did, - input: ToolsAtmoNotifsUpdateSettings.$input, -): Promise { + input: PubAtmoNotifyUpdateSettings.$input, +): Promise { await q.ensureUser(env.DB, did, now()); // Partial PATCH: only touch fields present in the input. @@ -150,7 +150,7 @@ export async function updateSettings( export async function listGrants( env: Env, did: Did, -): Promise { +): Promise { const rows = await q.listGrantsForRecipient(env.DB, did); return { @@ -172,7 +172,7 @@ export async function listGrants( export async function listPending( env: Env, did: Did, -): Promise { +): Promise { const rows = await q.listPendingForRecipient(env.DB, did, now()); return { @@ -196,7 +196,7 @@ export async function listPending( export async function listChannels( env: Env, did: Did, -): Promise { +): Promise { const rows = await q.listChannelsForDid(env.DB, did); return { @@ -211,7 +211,7 @@ export async function listChannels( export async function getSettings( env: Env, did: Did, -): Promise { +): Promise { // Ensure the row exists so we return stored defaults rather than guessing. await q.ensureUser(env.DB, did, now()); const user = await q.getUser(env.DB, did); diff --git a/apps/relay/src/telegram/commands.ts b/apps/relay/src/telegram/commands.ts index da8b56b..6c1acfa 100644 --- a/apps/relay/src/telegram/commands.ts +++ b/apps/relay/src/telegram/commands.ts @@ -12,8 +12,9 @@ import { now } from '../lib/time'; import type { TelegramMessage } from './webhook'; -// The dashboard is the web app (notify.atmo.tools), not the relay (notifs.atmo.tools). -const DASHBOARD_URL = 'https://notify.atmo.tools/dashboard'; +// Telegram linking lives in the web app's Channels settings (atmo.pub), not the +// relay (relay.atmo.pub). Deep-link straight to that tab. +const DASHBOARD_URL = 'https://atmo.pub/settings?tab=channels'; const PLATFORM = 'telegram'; /** Dispatch a `/command` message to its handler. */ @@ -190,8 +191,7 @@ export function settingsKeyboard(enabled: boolean): InlineKeyboardMarkup { }; } -const NOT_LINKED = - 'This Telegram account is not linked yet. Link it from https://notify.atmo.tools/dashboard'; +const NOT_LINKED = `This Telegram account is not linked yet. Link it from ${DASHBOARD_URL}`; function replyText(env: Env, chatId: number, text: string): Promise<{ message_id: number }> { return sendMessage(env, { chat_id: chatId, text }); diff --git a/apps/relay/src/well-known.ts b/apps/relay/src/well-known.ts index 23c83aa..de29c4f 100644 --- a/apps/relay/src/well-known.ts +++ b/apps/relay/src/well-known.ts @@ -1,5 +1,5 @@ -import requestPermission from '@atmo/notifs-lexicons/lexicons/tools/atmo/notifs/requestPermission.json'; -import send from '@atmo/notifs-lexicons/lexicons/tools/atmo/notifs/send.json'; +import requestPermission from '@atmo/notifs-lexicons/lexicons/pub/atmo/notify/requestPermission.json'; +import send from '@atmo/notifs-lexicons/lexicons/pub/atmo/notify/send.json'; import type { Env } from './env'; @@ -8,8 +8,8 @@ import type { Env } from './env'; // so their lexicon JSON is intentionally NOT served (the files remain in the // package purely as type-generation input). See src/rpc/entrypoint.ts. const LEXICONS: Record = { - 'tools.atmo.notifs.requestPermission': requestPermission, - 'tools.atmo.notifs.send': send, + 'pub.atmo.notify.requestPermission': requestPermission, + 'pub.atmo.notify.send': send, }; /** diff --git a/apps/relay/src/xrpc/requestPermission.ts b/apps/relay/src/xrpc/requestPermission.ts index fd29850..7dd13d4 100644 --- a/apps/relay/src/xrpc/requestPermission.ts +++ b/apps/relay/src/xrpc/requestPermission.ts @@ -1,4 +1,4 @@ -import { ToolsAtmoNotifsRequestPermission } from '@atmo/notifs-lexicons'; +import { PubAtmoNotifyRequestPermission } from '@atmo/notifs-lexicons'; import type { Did } from '@atcute/lexicons'; import { isDid } from '@atcute/lexicons/syntax'; import { InvalidRequestError, json, type ProcedureConfig } from '@atcute/xrpc-server'; @@ -13,7 +13,7 @@ import { isTrustedSender } from '../lib/trusted'; import { ensureSenderProfile } from '../profile/fetch'; import { checkAndIncrement } from '../ratelimit'; -const LXM = 'tools.atmo.notifs.requestPermission'; +const LXM = 'pub.atmo.notify.requestPermission'; // New pending-request caps, both per rolling hour. const PER_RECIPIENT_LIMIT = 50; // NEW: stops one OAuth'd app spamming a user @@ -22,7 +22,7 @@ const WINDOW_SECONDS = 60 * 60; export function makeRequestPermission( app: AppContext, -): ProcedureConfig { +): ProcedureConfig { return { handler: async ({ request, input }) => { // Auth flipped to the user path: the JWT issuer is the recipient (the user diff --git a/apps/relay/src/xrpc/send.ts b/apps/relay/src/xrpc/send.ts index 367373e..9d1a9d2 100644 --- a/apps/relay/src/xrpc/send.ts +++ b/apps/relay/src/xrpc/send.ts @@ -1,4 +1,4 @@ -import { ToolsAtmoNotifsSend } from '@atmo/notifs-lexicons'; +import { PubAtmoNotifySend } from '@atmo/notifs-lexicons'; import type { Did } from '@atcute/lexicons'; import { json, type ProcedureConfig } from '@atcute/xrpc-server'; @@ -10,7 +10,7 @@ import { newId } from '../lib/ids'; import { now } from '../lib/time'; import { checkAndIncrement } from '../ratelimit'; -const LXM = 'tools.atmo.notifs.send'; +const LXM = 'pub.atmo.notify.send'; // Per-pair limits: at most 1 notification/second and 100/day. const PER_SECOND_LIMIT = 1; @@ -18,7 +18,7 @@ const PER_SECOND_WINDOW = 1; const PER_DAY_LIMIT = 100; const PER_DAY_WINDOW = 24 * 60 * 60; -export function makeSend(app: AppContext): ProcedureConfig { +export function makeSend(app: AppContext): ProcedureConfig { return { handler: async ({ request, input }) => { const { senderDid } = await verifySenderRequest(app.verifier, request, LXM); diff --git a/apps/relay/test/auth.test.ts b/apps/relay/test/auth.test.ts index 1563a0a..1856348 100644 --- a/apps/relay/test/auth.test.ts +++ b/apps/relay/test/auth.test.ts @@ -24,7 +24,7 @@ beforeAll(() => { // exercises the user-JWT verifier now that the management methods moved to the // service binding. A valid body is sent in every case so the request reaches the // auth check rather than tripping schema validation first. -const REQ = 'tools.atmo.notifs.requestPermission'; +const REQ = 'pub.atmo.notify.requestPermission'; const SENDER: Did = 'did:plc:authsender'; const BODY = { senderDid: SENDER, title: 'Test' }; @@ -69,7 +69,7 @@ it('rejects a JWT scoped to a different lexicon method', async () => { const user = await makeIdentity('did:plc:authlxm'); mockPlc(user); // Token authorizes `send`, but we call `requestPermission`. - const jwt = await makeJwt(user, { lxm: 'tools.atmo.notifs.send' }); + const jwt = await makeJwt(user, { lxm: 'pub.atmo.notify.send' }); const res = await call(xrpcPost(REQ, jwt, BODY)); diff --git a/apps/relay/test/helpers.ts b/apps/relay/test/helpers.ts index ed9c885..dc7b1d3 100644 --- a/apps/relay/test/helpers.ts +++ b/apps/relay/test/helpers.ts @@ -2,7 +2,7 @@ import { P256PrivateKeyExportable } from '@atcute/crypto'; import type { Did, Nsid } from '@atcute/lexicons'; import { createServiceJwt } from '@atcute/xrpc-server/auth'; -export const RELAY_DID = 'did:web:notifs.atmo.tools'; +export const RELAY_DID = 'did:web:relay.atmo.pub'; // --------------------------------------------------------------------------- // Outbound fetch mocking @@ -140,7 +140,7 @@ export function makeJwt(identity: TestIdentity, options: JwtOptions): Promise { makeBskyProfileMock(); }); -const REQ = 'tools.atmo.notifs.requestPermission'; +const REQ = 'pub.atmo.notify.requestPermission'; // requestPermission is now user-authenticated; the sender is a plain DID in the body. const SENDER: Did = 'did:plc:somesender'; diff --git a/apps/relay/test/send.test.ts b/apps/relay/test/send.test.ts index b5e64f6..a70ce47 100644 --- a/apps/relay/test/send.test.ts +++ b/apps/relay/test/send.test.ts @@ -12,7 +12,7 @@ beforeAll(() => { mockTelegramOk(); }); -const SEND = 'tools.atmo.notifs.send'; +const SEND = 'pub.atmo.notify.send'; const RECIPIENT: Did = 'did:plc:sendrecipient'; async function call(req: Request): Promise { diff --git a/apps/relay/test/telegram-link.test.ts b/apps/relay/test/telegram-link.test.ts index f948de6..5fd9e69 100644 --- a/apps/relay/test/telegram-link.test.ts +++ b/apps/relay/test/telegram-link.test.ts @@ -22,7 +22,7 @@ async function call(req: Request): Promise { } function webhook(update: unknown, secret = SECRET): Request { - return new Request(`https://notifs.atmo.tools/telegram/webhook/${secret}`, { + return new Request(`https://relay.atmo.pub/telegram/webhook/${secret}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(update), diff --git a/apps/relay/wrangler.toml b/apps/relay/wrangler.toml index 8ebaa7f..e76655d 100644 --- a/apps/relay/wrangler.toml +++ b/apps/relay/wrangler.toml @@ -5,7 +5,7 @@ compatibility_flags = ["nodejs_compat"] # Custom domains bind the whole hostname (no path/wildcard); the Worker's own # router dispatches paths internally. -routes = [{ pattern = "notifs.atmo.tools", custom_domain = true }] +routes = [{ pattern = "relay.atmo.pub", custom_domain = true }] [[d1_databases]] binding = "DB" @@ -36,7 +36,7 @@ crons = ["0 3 * * *"] enabled = true [vars] -RELAY_DID = "did:web:notifs.atmo.tools" +RELAY_DID = "did:web:relay.atmo.pub" BOT_USERNAME = "atmo_notify_bot" # e.g. "atmonotifsbot" # Web push (VAPID). Generate with `pnpm vapid:keygen`. The public key is also the # browser applicationServerKey — paste the SAME value into apps/web VAPID_PUBLIC_KEY. diff --git a/apps/web/README.md b/apps/web/README.md index d534896..1a665fd 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -52,7 +52,7 @@ pnpm dev Set the env vars in `.env` (see `.env.example`): `ORIGIN`, `COOKIE_SECRET`, `CLIENT_ASSERTION_KEY`. The relay must be reachable at the domain in -`src/lib/config.ts` (`notifs.atmo.tools`) for dashboard data to load — point it at +`src/lib/config.ts` (`relay.atmo.pub`) for dashboard data to load — point it at a local relay during development if needed. ```sh @@ -66,7 +66,7 @@ Uses `@sveltejs/adapter-cloudflare` (Workers + static assets). Run these from `apps/web/` in a Cloudflare-authenticated terminal. 1. **Pick the domain.** In `wrangler.jsonc`, set `name` and `vars.ORIGIN` - (e.g. `https://notifs-web.atmo.tools`). `ORIGIN` must equal the served URL — + (e.g. `https://atmo.pub`). `ORIGIN` must equal the served URL — the atproto OAuth `client_id` is derived from it. 2. **Create the KV namespaces** and paste the ids into `wrangler.jsonc`: ```sh @@ -87,4 +87,4 @@ Uses `@sveltejs/adapter-cloudflare` (Workers + static assets). Run these from The OAuth client metadata is served dynamically by `@svelte-atproto/oauth` — no static file to write. The dashboard needs the relay reachable at the domain in -`src/lib/config.ts` (`notifs.atmo.tools`), so deploy the relay too. +`src/lib/config.ts` (`relay.atmo.pub`), so deploy the relay too. diff --git a/apps/web/src/lib/atproto/index.ts b/apps/web/src/lib/atproto/index.ts index 26e84a4..15dcf2d 100644 --- a/apps/web/src/lib/atproto/index.ts +++ b/apps/web/src/lib/atproto/index.ts @@ -10,7 +10,7 @@ export const atproto = createAtprotoAuth({ origin: env.ORIGIN, cookieSecret: env.COOKIE_SECRET, clientAssertionKey: env.CLIENT_ASSERTION_KEY, - // Requests the `tools.atmo.notifs.authUser` permission set scoped to the relay. + // Requests the `pub.atmo.notify.authUser` permission set scoped to the relay. scope: OAUTH_SCOPE, sessions: cloudflareKV('OAUTH_SESSIONS'), states: cloudflareKV('OAUTH_STATES', { ttl: 600 }) diff --git a/apps/web/src/lib/server/relay.ts b/apps/web/src/lib/server/relay.ts index 2021ddf..1fa8fde 100644 --- a/apps/web/src/lib/server/relay.ts +++ b/apps/web/src/lib/server/relay.ts @@ -15,13 +15,13 @@ import type { CategoryRoute, MarkReadInput, PushSubscriptionInput, - ToolsAtmoNotifsDenyPending, - ToolsAtmoNotifsGrant, - ToolsAtmoNotifsLinkChannel, - ToolsAtmoNotifsMuteGrant, - ToolsAtmoNotifsRevoke, - ToolsAtmoNotifsUnlinkChannel, - ToolsAtmoNotifsUpdateSettings + PubAtmoNotifyDenyPending, + PubAtmoNotifyGrant, + PubAtmoNotifyLinkChannel, + PubAtmoNotifyMuteGrant, + PubAtmoNotifyRevoke, + PubAtmoNotifyUnlinkChannel, + PubAtmoNotifyUpdateSettings } from '@atmo/notifs-lexicons'; /** @@ -45,13 +45,13 @@ export function relayFor(platform: App.Platform | undefined, did: Did | null) { listPending: () => svc.listPending(did), listChannels: () => svc.listChannels(did), getSettings: () => svc.getSettings(did), - grant: (input: ToolsAtmoNotifsGrant.$input) => svc.grant(did, input), - revoke: (input: ToolsAtmoNotifsRevoke.$input) => svc.revoke(did, input), - denyPending: (input: ToolsAtmoNotifsDenyPending.$input) => svc.denyPending(did, input), - muteGrant: (input: ToolsAtmoNotifsMuteGrant.$input) => svc.muteGrant(did, input), - linkChannel: (input: ToolsAtmoNotifsLinkChannel.$input) => svc.linkChannel(did, input), - unlinkChannel: (input: ToolsAtmoNotifsUnlinkChannel.$input) => svc.unlinkChannel(did, input), - updateSettings: (input: ToolsAtmoNotifsUpdateSettings.$input) => + grant: (input: PubAtmoNotifyGrant.$input) => svc.grant(did, input), + revoke: (input: PubAtmoNotifyRevoke.$input) => svc.revoke(did, input), + denyPending: (input: PubAtmoNotifyDenyPending.$input) => svc.denyPending(did, input), + muteGrant: (input: PubAtmoNotifyMuteGrant.$input) => svc.muteGrant(did, input), + linkChannel: (input: PubAtmoNotifyLinkChannel.$input) => svc.linkChannel(did, input), + unlinkChannel: (input: PubAtmoNotifyUnlinkChannel.$input) => svc.unlinkChannel(did, input), + updateSettings: (input: PubAtmoNotifyUpdateSettings.$input) => svc.updateSettings(did, input), registerWebPush: (sub: PushSubscriptionInput) => svc.registerWebPush(did, sub), unregisterWebPush: (endpoint: string) => svc.unregisterWebPush(did, endpoint), diff --git a/apps/web/wrangler.jsonc b/apps/web/wrangler.jsonc index 7d35d83..cca7c79 100644 --- a/apps/web/wrangler.jsonc +++ b/apps/web/wrangler.jsonc @@ -12,7 +12,7 @@ // Public origin of the deployed app (NOT secret). Must equal the domain you // serve from — the atproto OAuth client_id is derived from it. "vars": { - "ORIGIN": "https://notify.atmo.tools" + "ORIGIN": "https://atmo.pub" }, // KV namespaces required by @svelte-atproto/oauth's `cloudflareKV` stores // (see src/lib/atproto/index.ts). Create them and paste the ids: @@ -46,5 +46,5 @@ // CLIENT_ASSERTION_KEY (atproto-oauth keygen | wrangler secret put CLIENT_ASSERTION_KEY) // // Custom domain: bind it in the CF dashboard, or add: - // "routes": [{ "pattern": "notifs-web.atmo.tools", "custom_domain": true }] + // "routes": [{ "pattern": "atmo.pub", "custom_domain": true }] } \ No newline at end of file diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/denyPending.json b/packages/lexicons/lexicons/pub/atmo/notify/denyPending.json similarity index 94% rename from packages/lexicons/lexicons/tools/atmo/notifs/denyPending.json rename to packages/lexicons/lexicons/pub/atmo/notify/denyPending.json index eebda2a..0b4dac8 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/denyPending.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/denyPending.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.denyPending", + "id": "pub.atmo.notify.denyPending", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/getSettings.json b/packages/lexicons/lexicons/pub/atmo/notify/getSettings.json similarity index 95% rename from packages/lexicons/lexicons/tools/atmo/notifs/getSettings.json rename to packages/lexicons/lexicons/pub/atmo/notify/getSettings.json index ab7b595..2e67100 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/getSettings.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/getSettings.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.getSettings", + "id": "pub.atmo.notify.getSettings", "defs": { "main": { "type": "query", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/grant.json b/packages/lexicons/lexicons/pub/atmo/notify/grant.json similarity index 96% rename from packages/lexicons/lexicons/tools/atmo/notifs/grant.json rename to packages/lexicons/lexicons/pub/atmo/notify/grant.json index ae8216d..19a5e78 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/grant.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/grant.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.grant", + "id": "pub.atmo.notify.grant", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/linkChannel.json b/packages/lexicons/lexicons/pub/atmo/notify/linkChannel.json similarity index 95% rename from packages/lexicons/lexicons/tools/atmo/notifs/linkChannel.json rename to packages/lexicons/lexicons/pub/atmo/notify/linkChannel.json index 4a976f1..d4807e7 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/linkChannel.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/linkChannel.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.linkChannel", + "id": "pub.atmo.notify.linkChannel", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/listChannels.json b/packages/lexicons/lexicons/pub/atmo/notify/listChannels.json similarity index 94% rename from packages/lexicons/lexicons/tools/atmo/notifs/listChannels.json rename to packages/lexicons/lexicons/pub/atmo/notify/listChannels.json index e6b7ee5..79d2b45 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/listChannels.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/listChannels.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.listChannels", + "id": "pub.atmo.notify.listChannels", "defs": { "main": { "type": "query", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/listGrants.json b/packages/lexicons/lexicons/pub/atmo/notify/listGrants.json similarity index 97% rename from packages/lexicons/lexicons/tools/atmo/notifs/listGrants.json rename to packages/lexicons/lexicons/pub/atmo/notify/listGrants.json index 78b7543..a27d0f5 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/listGrants.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/listGrants.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.listGrants", + "id": "pub.atmo.notify.listGrants", "defs": { "main": { "type": "query", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/listPending.json b/packages/lexicons/lexicons/pub/atmo/notify/listPending.json similarity index 97% rename from packages/lexicons/lexicons/tools/atmo/notifs/listPending.json rename to packages/lexicons/lexicons/pub/atmo/notify/listPending.json index 85b4efc..bd3933f 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/listPending.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/listPending.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.listPending", + "id": "pub.atmo.notify.listPending", "defs": { "main": { "type": "query", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/muteGrant.json b/packages/lexicons/lexicons/pub/atmo/notify/muteGrant.json similarity index 95% rename from packages/lexicons/lexicons/tools/atmo/notifs/muteGrant.json rename to packages/lexicons/lexicons/pub/atmo/notify/muteGrant.json index dad0581..b8eec3f 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/muteGrant.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/muteGrant.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.muteGrant", + "id": "pub.atmo.notify.muteGrant", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/requestPermission.json b/packages/lexicons/lexicons/pub/atmo/notify/requestPermission.json similarity index 97% rename from packages/lexicons/lexicons/tools/atmo/notifs/requestPermission.json rename to packages/lexicons/lexicons/pub/atmo/notify/requestPermission.json index 10855e3..56d91bb 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/requestPermission.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/requestPermission.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.requestPermission", + "id": "pub.atmo.notify.requestPermission", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/revoke.json b/packages/lexicons/lexicons/pub/atmo/notify/revoke.json similarity index 95% rename from packages/lexicons/lexicons/tools/atmo/notifs/revoke.json rename to packages/lexicons/lexicons/pub/atmo/notify/revoke.json index bee6d51..665606f 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/revoke.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/revoke.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.revoke", + "id": "pub.atmo.notify.revoke", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/send.json b/packages/lexicons/lexicons/pub/atmo/notify/send.json similarity index 98% rename from packages/lexicons/lexicons/tools/atmo/notifs/send.json rename to packages/lexicons/lexicons/pub/atmo/notify/send.json index 8d61c8b..cf5fc67 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/send.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/send.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.send", + "id": "pub.atmo.notify.send", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/unlinkChannel.json b/packages/lexicons/lexicons/pub/atmo/notify/unlinkChannel.json similarity index 94% rename from packages/lexicons/lexicons/tools/atmo/notifs/unlinkChannel.json rename to packages/lexicons/lexicons/pub/atmo/notify/unlinkChannel.json index 7692a5e..74f93b1 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/unlinkChannel.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/unlinkChannel.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.unlinkChannel", + "id": "pub.atmo.notify.unlinkChannel", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/updateSettings.json b/packages/lexicons/lexicons/pub/atmo/notify/updateSettings.json similarity index 95% rename from packages/lexicons/lexicons/tools/atmo/notifs/updateSettings.json rename to packages/lexicons/lexicons/pub/atmo/notify/updateSettings.json index 1e07d8d..2c45c30 100644 --- a/packages/lexicons/lexicons/tools/atmo/notifs/updateSettings.json +++ b/packages/lexicons/lexicons/pub/atmo/notify/updateSettings.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "tools.atmo.notifs.updateSettings", + "id": "pub.atmo.notify.updateSettings", "defs": { "main": { "type": "procedure", diff --git a/packages/lexicons/src/index.ts b/packages/lexicons/src/index.ts index 4dab56c..b0d4578 100644 --- a/packages/lexicons/src/index.ts +++ b/packages/lexicons/src/index.ts @@ -1,7 +1,7 @@ // Public entrypoint for the shared lexicons package. // // Re-exports the code generated by `@atcute/lex-cli` (run `pnpm generate`). -// Each lexicon is exposed as a namespace, e.g. `ToolsAtmoNotifsSend`, carrying: +// Each lexicon is exposed as a namespace, e.g. `PubAtmoNotifySend`, carrying: // - `mainSchema` — the XRPC metadata you pass to `XRPCRouter.add*` // - `$input` / `$output` / `$params` — inferred request/response types // - view types for `ref`'d defs (e.g. `GrantView`) diff --git a/packages/lexicons/src/rpc.ts b/packages/lexicons/src/rpc.ts index 503744a..347a25b 100644 --- a/packages/lexicons/src/rpc.ts +++ b/packages/lexicons/src/rpc.ts @@ -14,17 +14,17 @@ import type { Did } from '@atcute/lexicons'; import type { - ToolsAtmoNotifsDenyPending, - ToolsAtmoNotifsGetSettings, - ToolsAtmoNotifsGrant, - ToolsAtmoNotifsLinkChannel, - ToolsAtmoNotifsListChannels, - ToolsAtmoNotifsListGrants, - ToolsAtmoNotifsListPending, - ToolsAtmoNotifsMuteGrant, - ToolsAtmoNotifsRevoke, - ToolsAtmoNotifsUnlinkChannel, - ToolsAtmoNotifsUpdateSettings, + PubAtmoNotifyDenyPending, + PubAtmoNotifyGetSettings, + PubAtmoNotifyGrant, + PubAtmoNotifyLinkChannel, + PubAtmoNotifyListChannels, + PubAtmoNotifyListGrants, + PubAtmoNotifyListPending, + PubAtmoNotifyMuteGrant, + PubAtmoNotifyRevoke, + PubAtmoNotifyUnlinkChannel, + PubAtmoNotifyUpdateSettings, } from './lexicons/index.js'; /** A browser PushSubscription, flattened (binding-only; no public lexicon). */ @@ -93,32 +93,32 @@ export interface RoutingConfig { } export interface NotifsRpc { - grant(did: Did, input: ToolsAtmoNotifsGrant.$input): Promise; - revoke(did: Did, input: ToolsAtmoNotifsRevoke.$input): Promise; + grant(did: Did, input: PubAtmoNotifyGrant.$input): Promise; + revoke(did: Did, input: PubAtmoNotifyRevoke.$input): Promise; denyPending( did: Did, - input: ToolsAtmoNotifsDenyPending.$input, - ): Promise; + input: PubAtmoNotifyDenyPending.$input, + ): Promise; muteGrant( did: Did, - input: ToolsAtmoNotifsMuteGrant.$input, - ): Promise; + input: PubAtmoNotifyMuteGrant.$input, + ): Promise; linkChannel( did: Did, - input: ToolsAtmoNotifsLinkChannel.$input, - ): Promise; + input: PubAtmoNotifyLinkChannel.$input, + ): Promise; unlinkChannel( did: Did, - input: ToolsAtmoNotifsUnlinkChannel.$input, - ): Promise; + input: PubAtmoNotifyUnlinkChannel.$input, + ): Promise; updateSettings( did: Did, - input: ToolsAtmoNotifsUpdateSettings.$input, - ): Promise; - listGrants(did: Did): Promise; - listPending(did: Did): Promise; - listChannels(did: Did): Promise; - getSettings(did: Did): Promise; + input: PubAtmoNotifyUpdateSettings.$input, + ): Promise; + listGrants(did: Did): Promise; + listPending(did: Did): Promise; + listChannels(did: Did): Promise; + getSettings(did: Did): Promise; // Web push (binding-only; no public lexicon). registerWebPush(did: Did, sub: PushSubscriptionInput): Promise<{ registered: boolean }>;