add createLexClient factory with strictResponseProcessing: false
lex-client's strict mode rejects the legacy blob reference format
({cid, mimeType}) still present in older records; the old stack
tolerated it. Route all client construction through the factory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {createContext, useCallback, useContext, useEffect, useMemo} from 'react'
|
||||
import * as AgeRange from 'expo-age-range'
|
||||
import {Client} from '@atproto/lex'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {getPreferences} from '@bsky.app/sdk'
|
||||
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
||||
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
|
||||
@@ -9,6 +9,7 @@ import debounce from 'lodash.debounce'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {createPersistedQueryStorage} from '#/lib/persisted-query-storage'
|
||||
import {getAge} from '#/lib/strings/time'
|
||||
import {
|
||||
@@ -99,9 +100,7 @@ export function setBirthdateForDid({
|
||||
export const configQueryKey = ['config']
|
||||
export async function getConfig() {
|
||||
if (debug.enabled) return debug.resolve(debug.config)
|
||||
const client = new Client({
|
||||
service: PUBLIC_BSKY_SERVICE,
|
||||
})
|
||||
const client = createLexClient({service: PUBLIC_BSKY_SERVICE})
|
||||
return await client.call(app.bsky.ageassurance.getConfig)
|
||||
}
|
||||
export function getConfigFromCache():
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {Platform} from 'react-native'
|
||||
import {Client} from '@atproto/lex'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {wait} from '#/lib/async/wait'
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
PUBLIC_APPVIEW_DID,
|
||||
} from '#/lib/constants'
|
||||
import {isNetworkError} from '#/lib/hooks/useCleanError'
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {usePdsClient} from '#/state/session'
|
||||
import {usePatchAgeAssuranceServerState} from '#/ageAssurance'
|
||||
import {logger} from '#/ageAssurance/logger'
|
||||
@@ -50,7 +50,7 @@ export function useBeginAgeAssurance() {
|
||||
* appview with the token as a static Authorization header (a raw client,
|
||||
* unlike a session, is allowed to preset that header).
|
||||
*/
|
||||
const scopedClient = new Client({
|
||||
const scopedClient = createLexClient({
|
||||
service: APPVIEW,
|
||||
headers: {authorization: `Bearer ${token}`},
|
||||
})
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
type Agent,
|
||||
type AgentOptions,
|
||||
Client,
|
||||
type ClientOptions,
|
||||
} from '@atproto/lex'
|
||||
|
||||
/**
|
||||
* App-standard factory for lex {@link Client}s. Use this instead of `new
|
||||
* Client(...)` so every client shares the same lenient response processing.
|
||||
*
|
||||
* lex-client defaults to strict Lex processing, which rejects responses
|
||||
* containing the LEGACY blob reference format (objects with `cid` and
|
||||
* `mimeType` properties instead of `$type: 'blob'`). Older records on the
|
||||
* network still carry these, and the old @atproto/api stack tolerated them, so
|
||||
* strict mode would be a behavior regression. Lenient mode also relaxes
|
||||
* datetime format checks (e.g. missing timezones) and blob MIME/size
|
||||
* constraints, again matching the old stack's tolerance. `Client.configure`
|
||||
* only accepts `appLabelers` globally, so the option is defaulted here, per
|
||||
* constructed client.
|
||||
*/
|
||||
export function createLexClient(
|
||||
agent: Agent | AgentOptions,
|
||||
options?: ClientOptions,
|
||||
): Client {
|
||||
return new Client(agent, {strictResponseProcessing: false, ...options})
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import {Client} from '@atproto/lex'
|
||||
|
||||
import {type SupportedMimeTypes, VIDEO_SERVICE} from '#/lib/constants'
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
|
||||
export const createVideoEndpointUrl = (
|
||||
route: string,
|
||||
@@ -25,7 +24,7 @@ export const createVideoEndpointUrl = (
|
||||
* `#/ageAssurance/useBeginAgeAssurance`.
|
||||
*/
|
||||
export function createVideoServiceClient(token: string) {
|
||||
return new Client({
|
||||
return createLexClient({
|
||||
service: VIDEO_SERVICE,
|
||||
headers: {authorization: `Bearer ${token}`},
|
||||
})
|
||||
@@ -37,9 +36,7 @@ export function createVideoServiceClient(token: string) {
|
||||
* `AtpAgent` at `VIDEO_SERVICE`.
|
||||
*/
|
||||
export function createTokenlessVideoServiceClient() {
|
||||
return new Client({
|
||||
service: VIDEO_SERVICE,
|
||||
})
|
||||
return createLexClient({service: VIDEO_SERVICE})
|
||||
}
|
||||
|
||||
export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {useCallback, useState} from 'react'
|
||||
import {Keyboard, View} from 'react-native'
|
||||
import {Client} from '@atproto/lex'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import * as EmailValidator from 'email-validator'
|
||||
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
@@ -55,7 +55,7 @@ export const ForgotPasswordForm = ({
|
||||
setIsProcessing(true)
|
||||
|
||||
try {
|
||||
const client = new Client({service: serviceUrl})
|
||||
const client = createLexClient({service: serviceUrl})
|
||||
await client.call(com.atproto.server.requestPasswordReset, {email})
|
||||
onEmailSent()
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Client} from '@atproto/lex'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
||||
import {logger} from '#/logger'
|
||||
@@ -62,7 +62,7 @@ export const SetNewPasswordForm = ({
|
||||
setIsProcessing(true)
|
||||
|
||||
try {
|
||||
const client = new Client({service: serviceUrl})
|
||||
const client = createLexClient({service: serviceUrl})
|
||||
await client.call(com.atproto.server.resetPassword, {
|
||||
token: formattedCode,
|
||||
password,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {Client} from '@atproto/lex'
|
||||
import {type DatetimeString, type HandleString} from '@atproto/syntax'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
PUBLIC_BSKY_SERVICE,
|
||||
} from '#/lib/constants'
|
||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {createFullHandle} from '#/lib/strings/handles'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {com} from '#/lexicons'
|
||||
@@ -81,7 +81,7 @@ export async function checkHandleAvailability(
|
||||
) {
|
||||
if (serviceDid === BSKY_SERVICE_DID) {
|
||||
// entryway has a special API for handle availability
|
||||
const client = new Client({service: BSKY_SERVICE})
|
||||
const client = createLexClient({service: BSKY_SERVICE})
|
||||
const data = await client.call(com.atproto.temp.checkHandleAvailability, {
|
||||
handle: handle as HandleString,
|
||||
birthDate: birthDate as DatetimeString | undefined,
|
||||
@@ -114,7 +114,7 @@ export async function checkHandleAvailability(
|
||||
}
|
||||
} else {
|
||||
// 3rd party PDSes won't have this API so just try and resolve the handle
|
||||
const client = new Client({service: PUBLIC_BSKY_SERVICE})
|
||||
const client = createLexClient({service: PUBLIC_BSKY_SERVICE})
|
||||
try {
|
||||
const res = await client.call(com.atproto.identity.resolveHandle, {
|
||||
handle: handle as HandleString,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {useCallback} from 'react'
|
||||
import {type $Typed, Client} from '@atproto/lex'
|
||||
import {type $Typed, type Client} from '@atproto/lex'
|
||||
import {toDatetimeString} from '@atproto/syntax'
|
||||
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {CHAT_SERVICE} from '#/lib/constants'
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries/index'
|
||||
import {createQueryKey, type StructuredQueryKey} from '#/state/queries/util'
|
||||
@@ -19,7 +20,7 @@ import * as bsky from '#/types/bsky'
|
||||
*/
|
||||
let publicChatClient: Client | undefined
|
||||
function getPublicChatClient(): Client {
|
||||
publicChatClient ??= new Client({service: CHAT_SERVICE})
|
||||
publicChatClient ??= createLexClient({service: CHAT_SERVICE})
|
||||
return publicChatClient
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {useState} from 'react'
|
||||
import {type DidDocument, getPdsEndpoint} from '@atproto/common-web'
|
||||
import {Client} from '@atproto/lex'
|
||||
import {type HandleString} from '@atproto/syntax'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {DEFAULT_SERVICE, PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
@@ -153,7 +153,7 @@ export async function resolvePdsForIdentifier(
|
||||
* Unauthenticated throwaway client pointed at the public appview -
|
||||
* resolveHandle is a public read.
|
||||
*/
|
||||
const client = new Client({service: PUBLIC_BSKY_SERVICE})
|
||||
const client = createLexClient({service: PUBLIC_BSKY_SERVICE})
|
||||
try {
|
||||
let did: string
|
||||
if (norm.startsWith('did:')) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Client} from '@atproto/lex'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {com} from '#/lexicons'
|
||||
|
||||
const RQKEY_ROOT = 'service'
|
||||
@@ -14,7 +14,7 @@ export function useServiceQuery(serviceUrl: string) {
|
||||
* Unauthenticated throwaway client pointed at the candidate service -
|
||||
* describeServer is a public endpoint on the target PDS/entryway.
|
||||
*/
|
||||
const client = new Client({service: serviceUrl})
|
||||
const client = createLexClient({service: serviceUrl})
|
||||
return await client.call(com.atproto.server.describeServer)
|
||||
},
|
||||
enabled: isValidUrl(serviceUrl),
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {Client} from '@atproto/lex'
|
||||
import {PasswordSession} from '@atproto/lex-password-session'
|
||||
|
||||
import {isJwtExpired} from '#/lib/jwt'
|
||||
import {createLexClient} from '#/lib/lexClient'
|
||||
import {type TemporaryPushClient} from '#/lib/notifications/notifications'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {networkAwareFetch, sessionAccountToSessionData} from './session-core'
|
||||
import {type SessionAccount} from './types'
|
||||
|
||||
/*
|
||||
* Canonical implementation moved to session-core.ts so that module stays
|
||||
* Canonical implementation lives in session-core.ts so that module stays
|
||||
* dependency-light (this file transitively pulls in a large chunk of the app).
|
||||
* Re-exported here for existing consumers.
|
||||
*/
|
||||
@@ -28,18 +28,14 @@ export function isSessionExpired(account: SessionAccount) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and resumes a throwaway session for every stored account.
|
||||
* Intended to send push token revocations just before logout.
|
||||
* Creates and resumes a throwaway session for every stored account. Intended to
|
||||
* send push token revocations just before logout.
|
||||
*
|
||||
* Each returned {@link TemporaryPushClient} wraps a temporary `PasswordSession`
|
||||
* resumed over the network to obtain a valid access token. These sessions are
|
||||
* resumed over the network for a valid access token. These sessions are
|
||||
* deliberately hook-free (no `onUpdated`/`onDeleted`): they must NEVER persist
|
||||
* or race the active session. They are used once for the unregister call and
|
||||
* discarded (reclaimed by GC), so we never call `logout()` on them.
|
||||
*
|
||||
* Each session is wrapped in a plain account-shaped `Client` (no proxy header)
|
||||
* paired with the account's service origin and handle, matching the contract
|
||||
* {@link unregisterPushToken} consumes.
|
||||
*/
|
||||
export async function createTemporaryClientsAndResume(
|
||||
accounts: SessionAccount[],
|
||||
@@ -51,7 +47,7 @@ export async function createTemporaryClientsAndResume(
|
||||
{fetch: networkAwareFetch},
|
||||
)
|
||||
return {
|
||||
client: new Client(session),
|
||||
client: createLexClient(session),
|
||||
service: session.session.service,
|
||||
handle: session.session.handle,
|
||||
} satisfies TemporaryPushClient
|
||||
|
||||
Reference in New Issue
Block a user