[SDK] Swap moderation to @bsky.app/sdk and move labeler config to Client statics (#11383)

This commit is contained in:
Samuel Newman
2026-08-13 22:26:21 +03:00
committed by GitHub
parent dc31e7fd12
commit 59d2bf09d7
138 changed files with 601 additions and 416 deletions
+41 -19
View File
@@ -31,18 +31,30 @@ const chatClients = new WeakMap<BskyAppAgent, Client>()
* routing. Because the agent already emits both headers, the client is
* deliberately built with neither a `service` option nor labelers - setting
* either here would emit them a second time.
*
* `appLabelers: null` suppresses the class-wide `Client.appLabelers` for this
* instance specifically. The static is populated (see
* `configureGlobalAppLabelers`) so that clients built without a wrapped agent
* carry the global authorities, but the agent already stamped those same DIDs
* onto the request, and lex would append its own copy on top: the agent joins
* its list with the existing header value while lex collects into a `Set` keyed
* on the suffixed string, so neither dedupes against the other and every global
* authority would appear twice.
*/
export function agentToAppviewClient(agent: BskyAppAgent): Client {
const existing = appviewClients.get(agent)
if (existing) {
return existing
}
const client = createLexClient({
get did() {
return agent.did
const client = createLexClient(
{
get did() {
return agent.did
},
fetchHandler: (path, init) => agent.fetchHandler(path, init),
},
fetchHandler: (path, init) => agent.fetchHandler(path, init),
})
{appLabelers: null},
)
appviewClients.set(agent, client)
return client
}
@@ -58,7 +70,10 @@ export function agentToAppviewClient(agent: BskyAppAgent): Client {
* for `com.atproto.*` repo/server/identity calls.
*
* No `service` option for the same reason: adding one would reintroduce the
* proxy header this client exists to avoid.
* proxy header this client exists to avoid. `appLabelers: null` is the same
* kind of suppression: a PDS request is not an appview read, so it must carry no
* moderation authorities at all - without this it would start emitting the
* global `Client.appLabelers`.
*
* The handler is wrapped in a closure rather than passed by reference because
* `PasswordSessionManager.fetchHandler` reads `this`. Relative paths are
@@ -71,12 +86,16 @@ export function agentToPdsClient(agent: BskyAppAgent): Client {
if (existing) {
return existing
}
const client = createLexClient({
get did() {
return agent.did
const client = createLexClient(
{
get did() {
return agent.did
},
fetchHandler: (path, init) =>
agent.sessionManager.fetchHandler(path, init),
},
fetchHandler: (path, init) => agent.sessionManager.fetchHandler(path, init),
})
{appLabelers: null},
)
pdsClients.set(agent, client)
return client
}
@@ -88,7 +107,8 @@ export function agentToPdsClient(agent: BskyAppAgent): Client {
* and PDS routing, no agent-level proxy or labeler headers - but constructed
* with {@link CHAT_PROXY_SERVICE} as its `service`, so lex-client emits
* `atproto-proxy: <CHAT_PROXY_SERVICE>` on every request and `chat.bsky.*`
* calls are proxied to the chat service.
* calls are proxied to the chat service. `appLabelers: null` for the same
* reason as the PDS client: the chat service takes no moderation authorities.
*/
export function agentToChatClient(agent: BskyAppAgent): Client {
const existing = chatClients.get(agent)
@@ -103,7 +123,7 @@ export function agentToChatClient(agent: BskyAppAgent): Client {
fetchHandler: (path, init) =>
agent.sessionManager.fetchHandler(path, init),
},
{service: CHAT_PROXY_SERVICE},
{appLabelers: null, service: CHAT_PROXY_SERVICE},
)
chatClients.set(agent, client)
return client
@@ -150,12 +170,14 @@ let publicLexClient: Client | undefined
* {@link networkAwareFetch} so public reads feed the app's reachability signal
* like authenticated ones do.
*
* Unlike the public agent it parallels, this client sends neither
* `atproto-proxy` nor `atproto-accept-labelers`. `createPublicAgent` configures
* the app labeler and the proxy header, so a logged-out appview *agent* read
* does carry labelers while the same read through this client does not. A
* consumer that needs moderation labels on public reads must configure labelers
* itself before issuing the request.
* Unlike the agent-wrapping clients, this one does NOT suppress
* `Client.appLabelers`: there is no agent underneath to stamp the header, so the
* class-wide static is the only producer and a logged-out read carries the same
* `;redact` moderation authorities an authenticated one does.
*
* That makes `configureModerationForGuest()` load-bearing rather than
* test-only - it is what populates the static before this client's first
* request. `createPublicSessionBundle` runs it while building the bundle.
*/
export function getPublicAppviewClient(): Client {
return (publicLexClient ??= createLexClient({