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 {createContext, useCallback, useContext, useEffect, useMemo} from 'react'
|
||||||
import * as AgeRange from 'expo-age-range'
|
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 {getPreferences} from '@bsky.app/sdk'
|
||||||
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
||||||
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
|
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
|
||||||
@@ -9,6 +9,7 @@ import debounce from 'lodash.debounce'
|
|||||||
|
|
||||||
import {networkRetry} from '#/lib/async/retry'
|
import {networkRetry} from '#/lib/async/retry'
|
||||||
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {createPersistedQueryStorage} from '#/lib/persisted-query-storage'
|
import {createPersistedQueryStorage} from '#/lib/persisted-query-storage'
|
||||||
import {getAge} from '#/lib/strings/time'
|
import {getAge} from '#/lib/strings/time'
|
||||||
import {
|
import {
|
||||||
@@ -99,9 +100,7 @@ export function setBirthdateForDid({
|
|||||||
export const configQueryKey = ['config']
|
export const configQueryKey = ['config']
|
||||||
export async function getConfig() {
|
export async function getConfig() {
|
||||||
if (debug.enabled) return debug.resolve(debug.config)
|
if (debug.enabled) return debug.resolve(debug.config)
|
||||||
const client = new Client({
|
const client = createLexClient({service: PUBLIC_BSKY_SERVICE})
|
||||||
service: PUBLIC_BSKY_SERVICE,
|
|
||||||
})
|
|
||||||
return await client.call(app.bsky.ageassurance.getConfig)
|
return await client.call(app.bsky.ageassurance.getConfig)
|
||||||
}
|
}
|
||||||
export function getConfigFromCache():
|
export function getConfigFromCache():
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {Platform} from 'react-native'
|
import {Platform} from 'react-native'
|
||||||
import {Client} from '@atproto/lex'
|
|
||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {wait} from '#/lib/async/wait'
|
import {wait} from '#/lib/async/wait'
|
||||||
@@ -9,6 +8,7 @@ import {
|
|||||||
PUBLIC_APPVIEW_DID,
|
PUBLIC_APPVIEW_DID,
|
||||||
} from '#/lib/constants'
|
} from '#/lib/constants'
|
||||||
import {isNetworkError} from '#/lib/hooks/useCleanError'
|
import {isNetworkError} from '#/lib/hooks/useCleanError'
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {usePdsClient} from '#/state/session'
|
import {usePdsClient} from '#/state/session'
|
||||||
import {usePatchAgeAssuranceServerState} from '#/ageAssurance'
|
import {usePatchAgeAssuranceServerState} from '#/ageAssurance'
|
||||||
import {logger} from '#/ageAssurance/logger'
|
import {logger} from '#/ageAssurance/logger'
|
||||||
@@ -50,7 +50,7 @@ export function useBeginAgeAssurance() {
|
|||||||
* appview with the token as a static Authorization header (a raw client,
|
* appview with the token as a static Authorization header (a raw client,
|
||||||
* unlike a session, is allowed to preset that header).
|
* unlike a session, is allowed to preset that header).
|
||||||
*/
|
*/
|
||||||
const scopedClient = new Client({
|
const scopedClient = createLexClient({
|
||||||
service: APPVIEW,
|
service: APPVIEW,
|
||||||
headers: {authorization: `Bearer ${token}`},
|
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 {type SupportedMimeTypes, VIDEO_SERVICE} from '#/lib/constants'
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
|
|
||||||
export const createVideoEndpointUrl = (
|
export const createVideoEndpointUrl = (
|
||||||
route: string,
|
route: string,
|
||||||
@@ -25,7 +24,7 @@ export const createVideoEndpointUrl = (
|
|||||||
* `#/ageAssurance/useBeginAgeAssurance`.
|
* `#/ageAssurance/useBeginAgeAssurance`.
|
||||||
*/
|
*/
|
||||||
export function createVideoServiceClient(token: string) {
|
export function createVideoServiceClient(token: string) {
|
||||||
return new Client({
|
return createLexClient({
|
||||||
service: VIDEO_SERVICE,
|
service: VIDEO_SERVICE,
|
||||||
headers: {authorization: `Bearer ${token}`},
|
headers: {authorization: `Bearer ${token}`},
|
||||||
})
|
})
|
||||||
@@ -37,9 +36,7 @@ export function createVideoServiceClient(token: string) {
|
|||||||
* `AtpAgent` at `VIDEO_SERVICE`.
|
* `AtpAgent` at `VIDEO_SERVICE`.
|
||||||
*/
|
*/
|
||||||
export function createTokenlessVideoServiceClient() {
|
export function createTokenlessVideoServiceClient() {
|
||||||
return new Client({
|
return createLexClient({service: VIDEO_SERVICE})
|
||||||
service: VIDEO_SERVICE,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) {
|
export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {useCallback, useState} from 'react'
|
import {useCallback, useState} from 'react'
|
||||||
import {Keyboard, View} from 'react-native'
|
import {Keyboard, View} from 'react-native'
|
||||||
import {Client} from '@atproto/lex'
|
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import * as EmailValidator from 'email-validator'
|
import * as EmailValidator from 'email-validator'
|
||||||
|
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
@@ -55,7 +55,7 @@ export const ForgotPasswordForm = ({
|
|||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = new Client({service: serviceUrl})
|
const client = createLexClient({service: serviceUrl})
|
||||||
await client.call(com.atproto.server.requestPasswordReset, {email})
|
await client.call(com.atproto.server.requestPasswordReset, {email})
|
||||||
onEmailSent()
|
onEmailSent()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {Client} from '@atproto/lex'
|
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
@@ -62,7 +62,7 @@ export const SetNewPasswordForm = ({
|
|||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = new Client({service: serviceUrl})
|
const client = createLexClient({service: serviceUrl})
|
||||||
await client.call(com.atproto.server.resetPassword, {
|
await client.call(com.atproto.server.resetPassword, {
|
||||||
token: formattedCode,
|
token: formattedCode,
|
||||||
password,
|
password,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import {Client} from '@atproto/lex'
|
|
||||||
import {type DatetimeString, type HandleString} from '@atproto/syntax'
|
import {type DatetimeString, type HandleString} from '@atproto/syntax'
|
||||||
import {useQuery} from '@tanstack/react-query'
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
@@ -8,6 +7,7 @@ import {
|
|||||||
PUBLIC_BSKY_SERVICE,
|
PUBLIC_BSKY_SERVICE,
|
||||||
} from '#/lib/constants'
|
} from '#/lib/constants'
|
||||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {createFullHandle} from '#/lib/strings/handles'
|
import {createFullHandle} from '#/lib/strings/handles'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {com} from '#/lexicons'
|
import {com} from '#/lexicons'
|
||||||
@@ -81,7 +81,7 @@ export async function checkHandleAvailability(
|
|||||||
) {
|
) {
|
||||||
if (serviceDid === BSKY_SERVICE_DID) {
|
if (serviceDid === BSKY_SERVICE_DID) {
|
||||||
// entryway has a special API for handle availability
|
// 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, {
|
const data = await client.call(com.atproto.temp.checkHandleAvailability, {
|
||||||
handle: handle as HandleString,
|
handle: handle as HandleString,
|
||||||
birthDate: birthDate as DatetimeString | undefined,
|
birthDate: birthDate as DatetimeString | undefined,
|
||||||
@@ -114,7 +114,7 @@ export async function checkHandleAvailability(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 3rd party PDSes won't have this API so just try and resolve the handle
|
// 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 {
|
try {
|
||||||
const res = await client.call(com.atproto.identity.resolveHandle, {
|
const res = await client.call(com.atproto.identity.resolveHandle, {
|
||||||
handle: handle as HandleString,
|
handle: handle as HandleString,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import {useCallback} from 'react'
|
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 {toDatetimeString} from '@atproto/syntax'
|
||||||
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
|
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {CHAT_SERVICE} from '#/lib/constants'
|
import {CHAT_SERVICE} from '#/lib/constants'
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {STALE} from '#/state/queries/index'
|
import {STALE} from '#/state/queries/index'
|
||||||
import {createQueryKey, type StructuredQueryKey} from '#/state/queries/util'
|
import {createQueryKey, type StructuredQueryKey} from '#/state/queries/util'
|
||||||
@@ -19,7 +20,7 @@ import * as bsky from '#/types/bsky'
|
|||||||
*/
|
*/
|
||||||
let publicChatClient: Client | undefined
|
let publicChatClient: Client | undefined
|
||||||
function getPublicChatClient(): Client {
|
function getPublicChatClient(): Client {
|
||||||
publicChatClient ??= new Client({service: CHAT_SERVICE})
|
publicChatClient ??= createLexClient({service: CHAT_SERVICE})
|
||||||
return publicChatClient
|
return publicChatClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
import {type DidDocument, getPdsEndpoint} from '@atproto/common-web'
|
import {type DidDocument, getPdsEndpoint} from '@atproto/common-web'
|
||||||
import {Client} from '@atproto/lex'
|
|
||||||
import {type HandleString} from '@atproto/syntax'
|
import {type HandleString} from '@atproto/syntax'
|
||||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {DEFAULT_SERVICE, PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
import {DEFAULT_SERVICE, PUBLIC_BSKY_SERVICE} from '#/lib/constants'
|
||||||
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
import {useDebouncedValue} from '#/lib/hooks/useDebouncedValue'
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
@@ -153,7 +153,7 @@ export async function resolvePdsForIdentifier(
|
|||||||
* Unauthenticated throwaway client pointed at the public appview -
|
* Unauthenticated throwaway client pointed at the public appview -
|
||||||
* resolveHandle is a public read.
|
* resolveHandle is a public read.
|
||||||
*/
|
*/
|
||||||
const client = new Client({service: PUBLIC_BSKY_SERVICE})
|
const client = createLexClient({service: PUBLIC_BSKY_SERVICE})
|
||||||
try {
|
try {
|
||||||
let did: string
|
let did: string
|
||||||
if (norm.startsWith('did:')) {
|
if (norm.startsWith('did:')) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {Client} from '@atproto/lex'
|
|
||||||
import {useQuery} from '@tanstack/react-query'
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {com} from '#/lexicons'
|
import {com} from '#/lexicons'
|
||||||
|
|
||||||
const RQKEY_ROOT = 'service'
|
const RQKEY_ROOT = 'service'
|
||||||
@@ -14,7 +14,7 @@ export function useServiceQuery(serviceUrl: string) {
|
|||||||
* Unauthenticated throwaway client pointed at the candidate service -
|
* Unauthenticated throwaway client pointed at the candidate service -
|
||||||
* describeServer is a public endpoint on the target PDS/entryway.
|
* 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)
|
return await client.call(com.atproto.server.describeServer)
|
||||||
},
|
},
|
||||||
enabled: isValidUrl(serviceUrl),
|
enabled: isValidUrl(serviceUrl),
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import {Client} from '@atproto/lex'
|
|
||||||
import {PasswordSession} from '@atproto/lex-password-session'
|
import {PasswordSession} from '@atproto/lex-password-session'
|
||||||
|
|
||||||
import {isJwtExpired} from '#/lib/jwt'
|
import {isJwtExpired} from '#/lib/jwt'
|
||||||
|
import {createLexClient} from '#/lib/lexClient'
|
||||||
import {type TemporaryPushClient} from '#/lib/notifications/notifications'
|
import {type TemporaryPushClient} from '#/lib/notifications/notifications'
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {networkAwareFetch, sessionAccountToSessionData} from './session-core'
|
import {networkAwareFetch, sessionAccountToSessionData} from './session-core'
|
||||||
import {type SessionAccount} from './types'
|
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).
|
* dependency-light (this file transitively pulls in a large chunk of the app).
|
||||||
* Re-exported here for existing consumers.
|
* 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.
|
* Creates and resumes a throwaway session for every stored account. Intended to
|
||||||
* Intended to send push token revocations just before logout.
|
* send push token revocations just before logout.
|
||||||
*
|
*
|
||||||
* Each returned {@link TemporaryPushClient} wraps a temporary `PasswordSession`
|
* 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
|
* deliberately hook-free (no `onUpdated`/`onDeleted`): they must NEVER persist
|
||||||
* or race the active session. They are used once for the unregister call and
|
* 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.
|
* 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(
|
export async function createTemporaryClientsAndResume(
|
||||||
accounts: SessionAccount[],
|
accounts: SessionAccount[],
|
||||||
@@ -51,7 +47,7 @@ export async function createTemporaryClientsAndResume(
|
|||||||
{fetch: networkAwareFetch},
|
{fetch: networkAwareFetch},
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
client: new Client(session),
|
client: createLexClient(session),
|
||||||
service: session.session.service,
|
service: session.session.service,
|
||||||
handle: session.session.handle,
|
handle: session.session.handle,
|
||||||
} satisfies TemporaryPushClient
|
} satisfies TemporaryPushClient
|
||||||
|
|||||||
Reference in New Issue
Block a user