@atproto/lex-client 0.0.10 → 0.0.12
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.
- package/CHANGELOG.md +32 -0
- package/dist/agent.d.ts +72 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +46 -1
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +442 -46
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +145 -1
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +202 -48
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +208 -65
- package/dist/errors.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +20 -20
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +12 -12
- package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +6 -6
- package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +6 -6
- package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +22 -22
- package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +2 -2
- package/dist/response.d.ts +17 -6
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +45 -32
- package/dist/response.js.map +1 -1
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +40 -5
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +22 -0
- package/dist/util.js.map +1 -1
- package/dist/www-authenticate.d.ts +35 -0
- package/dist/www-authenticate.d.ts.map +1 -0
- package/dist/www-authenticate.js +57 -0
- package/dist/www-authenticate.js.map +1 -0
- package/dist/xrpc.d.ts +82 -10
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +15 -28
- package/dist/xrpc.js.map +1 -1
- package/package.json +7 -7
- package/src/agent.ts +101 -1
- package/src/client.ts +428 -15
- package/src/errors.ts +308 -120
- package/src/response.ts +68 -63
- package/src/types.ts +52 -0
- package/src/util.ts +50 -5
- package/src/www-authenticate.test.ts +227 -0
- package/src/www-authenticate.ts +101 -0
- package/src/xrpc.ts +100 -53
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
type WWWAuthenticateParams = { [authParam in string]: string }
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parsed representation of a WWW-Authenticate HTTP header.
|
|
5
|
+
*
|
|
6
|
+
* Maps authentication scheme names to either:
|
|
7
|
+
* - A token68 string (compact authentication data)
|
|
8
|
+
* - A params object with key-value pairs
|
|
9
|
+
*
|
|
10
|
+
* @example Bearer with realm
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // WWW-Authenticate: Bearer realm="example"
|
|
13
|
+
* const parsed: WWWAuthenticate = {
|
|
14
|
+
* Bearer: { realm: 'example' }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @example DPoP with error
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // WWW-Authenticate: DPoP error="use_dpop_nonce", error_description="..."
|
|
21
|
+
* const parsed: WWWAuthenticate = {
|
|
22
|
+
* DPoP: { error: 'use_dpop_nonce', error_description: '...' }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export type WWWAuthenticate = {
|
|
27
|
+
[authScheme in string]:
|
|
28
|
+
| string // token68
|
|
29
|
+
| WWWAuthenticateParams
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns `undefined` if the header is malformed.
|
|
34
|
+
*/
|
|
35
|
+
export function parseWWWAuthenticateHeader(
|
|
36
|
+
header?: unknown,
|
|
37
|
+
): undefined | WWWAuthenticate {
|
|
38
|
+
if (typeof header !== 'string') return undefined
|
|
39
|
+
|
|
40
|
+
const wwwAuthenticate: WWWAuthenticate = {}
|
|
41
|
+
|
|
42
|
+
// Split over commas, not within quoted strings
|
|
43
|
+
const trimmedHeader = header.trim()
|
|
44
|
+
if (!trimmedHeader) return wwwAuthenticate
|
|
45
|
+
|
|
46
|
+
const parts = trimmedHeader.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/)
|
|
47
|
+
|
|
48
|
+
let currentParams: WWWAuthenticateParams | null = null
|
|
49
|
+
|
|
50
|
+
for (let part of parts) {
|
|
51
|
+
// Check if the part starts with an auth scheme
|
|
52
|
+
const schemeMatch = part.trim().match(/^([^"=\s]+)(\s+.*)?$/)
|
|
53
|
+
if (schemeMatch) {
|
|
54
|
+
const scheme = schemeMatch[1]
|
|
55
|
+
|
|
56
|
+
// Duplicate scheme
|
|
57
|
+
if (Object.hasOwn(wwwAuthenticate, scheme)) return undefined
|
|
58
|
+
|
|
59
|
+
const rest = schemeMatch[2]?.trim()
|
|
60
|
+
if (!rest) {
|
|
61
|
+
// Scheme only (no params or token68)
|
|
62
|
+
currentParams = null
|
|
63
|
+
wwwAuthenticate[scheme] = Object.create(null)
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!rest.includes('=')) {
|
|
68
|
+
// Scheme with token68
|
|
69
|
+
currentParams = null
|
|
70
|
+
wwwAuthenticate[scheme] = rest
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Scheme with params
|
|
75
|
+
|
|
76
|
+
currentParams = Object.create(null) as WWWAuthenticateParams
|
|
77
|
+
wwwAuthenticate[scheme] = currentParams
|
|
78
|
+
|
|
79
|
+
// Fall through to parse params
|
|
80
|
+
part = rest
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Invalid header
|
|
84
|
+
if (!currentParams) return undefined
|
|
85
|
+
|
|
86
|
+
const param = part.match(
|
|
87
|
+
/^\s*([^"\s=]+)=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([^\s,"]*))\s*$/,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// invalid param
|
|
91
|
+
if (!param) return undefined
|
|
92
|
+
|
|
93
|
+
const paramName = param[1]
|
|
94
|
+
const paramValue =
|
|
95
|
+
param[3] ?? param[2]!.slice(1, -1).replaceAll(/\\(.)/g, '$1')
|
|
96
|
+
|
|
97
|
+
currentParams[paramName] = paramValue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return wwwAuthenticate
|
|
101
|
+
}
|
package/src/xrpc.ts
CHANGED
|
@@ -13,15 +13,10 @@ import {
|
|
|
13
13
|
getMain,
|
|
14
14
|
} from '@atproto/lex-schema'
|
|
15
15
|
import { Agent } from './agent.js'
|
|
16
|
-
import {
|
|
17
|
-
XrpcResponseError,
|
|
18
|
-
XrpcUnexpectedError,
|
|
19
|
-
XrpcUpstreamError,
|
|
20
|
-
} from './errors.js'
|
|
16
|
+
import { XrpcFailure, asXrpcFailure } from './errors.js'
|
|
21
17
|
import { XrpcResponse } from './response.js'
|
|
22
18
|
import { BinaryBodyInit, CallOptions } from './types.js'
|
|
23
19
|
import {
|
|
24
|
-
XrpcPayload,
|
|
25
20
|
buildAtprotoHeaders,
|
|
26
21
|
isAsyncIterable,
|
|
27
22
|
isBlobLike,
|
|
@@ -32,6 +27,11 @@ import {
|
|
|
32
27
|
type XrpcParamsOptions<P extends Params> =
|
|
33
28
|
NonNullable<unknown> extends P ? { params?: P } : { params: P }
|
|
34
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The query/path parameters type for an XRPC method, inferred from its schema.
|
|
32
|
+
*
|
|
33
|
+
* @typeParam M - The XRPC method type (Procedure, Query, or Subscription)
|
|
34
|
+
*/
|
|
35
35
|
export type XrpcRequestParams<M extends Procedure | Query | Subscription> =
|
|
36
36
|
InferInput<M['parameters']>
|
|
37
37
|
|
|
@@ -44,39 +44,54 @@ type XrpcInputOptions<In> = In extends { body: infer B; encoding: infer E }
|
|
|
44
44
|
{ body: B; encoding?: E }
|
|
45
45
|
: { body?: undefined; encoding?: undefined }
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Options for making an XRPC request.
|
|
49
|
+
*
|
|
50
|
+
* Combines {@link CallOptions} with method-specific params and body requirements.
|
|
51
|
+
* The type system ensures required params/body are provided based on the method schema.
|
|
52
|
+
*
|
|
53
|
+
* @typeParam M - The XRPC method type (Procedure or Query)
|
|
54
|
+
* @see {@link CallOptions} for general request options like signal and validateRequest
|
|
55
|
+
* @see {@link XrpcParamsOptions} for method-specific query parameters
|
|
56
|
+
* @see {@link XrpcInputOptions} for method-specific body and encoding requirements
|
|
57
|
+
*
|
|
58
|
+
* @example Query with params
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const options: XrpcOptions<typeof app.bsky.feed.getTimeline.main> = {
|
|
61
|
+
* params: { limit: 50 }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @example Procedure with body
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const options: XrpcOptions<typeof com.atproto.repo.createRecord.main> = {
|
|
68
|
+
* body: { repo: did, collection: 'app.bsky.feed.post', record: { ... } }
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
47
72
|
export type XrpcOptions<M extends Procedure | Query = Procedure | Query> =
|
|
48
73
|
CallOptions &
|
|
49
74
|
XrpcInputOptions<XrpcRequestPayload<M>> &
|
|
50
75
|
XrpcParamsOptions<XrpcRequestParams<M>>
|
|
51
76
|
|
|
52
|
-
export type XrpcFailure<M extends Procedure | Query> =
|
|
53
|
-
// The server returned a valid XRPC error response
|
|
54
|
-
| XrpcResponseError<M>
|
|
55
|
-
// The response was not a valid XRPC response, or it does not match the schema
|
|
56
|
-
| XrpcUpstreamError
|
|
57
|
-
// Something went wrong (network error, etc.)
|
|
58
|
-
| XrpcUnexpectedError
|
|
59
|
-
|
|
60
|
-
export type XrpcResult<M extends Procedure | Query> =
|
|
61
|
-
| XrpcResponse<M>
|
|
62
|
-
| XrpcFailure<M>
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Utility method to type cast the error thrown by {@link xrpc} to an
|
|
66
|
-
* {@link XrpcFailure} matching the provided method. Only use this function
|
|
67
|
-
* inside a catch block right after calling {@link xrpc}, and use the same
|
|
68
|
-
* method type parameter as used in the {@link xrpc} call.
|
|
69
|
-
*/
|
|
70
|
-
export function asXrpcFailure<M extends Procedure | Query = Procedure | Query>(
|
|
71
|
-
err: unknown,
|
|
72
|
-
): XrpcFailure<M> {
|
|
73
|
-
if (err instanceof XrpcResponseError) return err
|
|
74
|
-
if (err instanceof XrpcUpstreamError) return err
|
|
75
|
-
return XrpcUnexpectedError.from(err)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
77
|
/**
|
|
79
|
-
*
|
|
78
|
+
* Makes an XRPC request and throws on failure.
|
|
79
|
+
*
|
|
80
|
+
* This is the low-level function for making XRPC calls. For most use cases,
|
|
81
|
+
* prefer using {@link Client.xrpc} which provides a more ergonomic API.
|
|
82
|
+
*
|
|
83
|
+
* @param agent - The {@link Agent} to use for making the request
|
|
84
|
+
* @param ns - The lexicon method definition
|
|
85
|
+
* @param options - Request {@link XrpcOptions options} (params, body, headers, etc.)
|
|
86
|
+
* @returns The successful {@link XrpcResponse}
|
|
87
|
+
* @throws {XrpcFailure} When the request fails
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const response = await xrpc(agent, app.bsky.feed.getTimeline.main, {
|
|
92
|
+
* params: { limit: 50 }
|
|
93
|
+
* })
|
|
94
|
+
* ```
|
|
80
95
|
*/
|
|
81
96
|
export async function xrpc<const M extends Query | Procedure>(
|
|
82
97
|
agent: Agent,
|
|
@@ -94,13 +109,49 @@ export async function xrpc<const M extends Query | Procedure>(
|
|
|
94
109
|
ns: Main<M>,
|
|
95
110
|
options: XrpcOptions<M> = {} as XrpcOptions<M>,
|
|
96
111
|
): Promise<XrpcResponse<M>> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
throw asXrpcFailure<M>(err)
|
|
101
|
-
}
|
|
112
|
+
const response = await xrpcSafe<M>(agent, ns, options)
|
|
113
|
+
if (response.success) return response
|
|
114
|
+
else throw response
|
|
102
115
|
}
|
|
103
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Union type representing either a successful response or a failure.
|
|
119
|
+
*
|
|
120
|
+
* Both {@link XrpcResponse} and {@link XrpcFailure} have a `success` property
|
|
121
|
+
* that can be used to discriminate between them.
|
|
122
|
+
*
|
|
123
|
+
* @typeParam M - The XRPC method type
|
|
124
|
+
*/
|
|
125
|
+
export type XrpcResult<M extends Procedure | Query> =
|
|
126
|
+
| XrpcResponse<M>
|
|
127
|
+
| XrpcFailure<M>
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Makes an XRPC request without throwing on failure.
|
|
131
|
+
*
|
|
132
|
+
* Returns a discriminated union that can be checked via the `success` property.
|
|
133
|
+
* This is useful for handling errors without try/catch blocks. This also allow
|
|
134
|
+
* failure results to be typed with the method schema, which can provide better
|
|
135
|
+
* type safety when handling errors (e.g. checking for specific error codes).
|
|
136
|
+
*
|
|
137
|
+
* @param agent - The {@link Agent} to use for making the request
|
|
138
|
+
* @param ns - The lexicon method definition
|
|
139
|
+
* @param options - Request {@link XrpcOptions options} (params, body, headers, etc.)
|
|
140
|
+
* @returns Either a successful {@link XrpcResponse} or an {@link XrpcFailure}
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* const result = await xrpcSafe(agent, app.bsky.actor.getProfile.main, {
|
|
145
|
+
* params: { actor: 'alice.bsky.social' }
|
|
146
|
+
* })
|
|
147
|
+
*
|
|
148
|
+
* if (result.success) {
|
|
149
|
+
* console.log(result.body.displayName)
|
|
150
|
+
* } else {
|
|
151
|
+
* console.error('Request failed:', result.error)
|
|
152
|
+
* }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
104
155
|
export async function xrpcSafe<const M extends Query | Procedure>(
|
|
105
156
|
agent: Agent,
|
|
106
157
|
ns: NonNullable<unknown> extends XrpcOptions<M>
|
|
@@ -117,20 +168,16 @@ export async function xrpcSafe<const M extends Query | Procedure>(
|
|
|
117
168
|
ns: Main<M>,
|
|
118
169
|
options: XrpcOptions<M> = {} as XrpcOptions<M>,
|
|
119
170
|
): Promise<XrpcResult<M>> {
|
|
120
|
-
return lexRpcRequest<M>(agent, ns, options).catch(asXrpcFailure<M>)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function lexRpcRequest<const M extends Query | Procedure>(
|
|
124
|
-
agent: Agent,
|
|
125
|
-
ns: Main<M>,
|
|
126
|
-
options: XrpcOptions<M> = {} as XrpcOptions<M>,
|
|
127
|
-
): Promise<XrpcResponse<M>> {
|
|
128
|
-
const method = getMain(ns)
|
|
129
171
|
options.signal?.throwIfAborted()
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
172
|
+
const method: M = getMain(ns)
|
|
173
|
+
try {
|
|
174
|
+
const url = xrpcRequestUrl(method, options)
|
|
175
|
+
const request = xrpcRequestInit(method, options)
|
|
176
|
+
const response = await agent.fetchHandler(url, request)
|
|
177
|
+
return await XrpcResponse.fromFetchResponse<M>(method, response, options)
|
|
178
|
+
} catch (cause) {
|
|
179
|
+
return asXrpcFailure(method, cause)
|
|
180
|
+
}
|
|
134
181
|
}
|
|
135
182
|
|
|
136
183
|
function xrpcRequestUrl<M extends Procedure | Query | Subscription>(
|
|
@@ -205,7 +252,7 @@ function xrpcProcedureInput(
|
|
|
205
252
|
method: Procedure,
|
|
206
253
|
options: CallOptions & { body?: LexValue | BinaryBodyInit },
|
|
207
254
|
encodingHint?: string,
|
|
208
|
-
): null |
|
|
255
|
+
): null | { body: BodyInit; encoding: string } {
|
|
209
256
|
const { input } = method
|
|
210
257
|
const { body } = options
|
|
211
258
|
|
|
@@ -254,7 +301,7 @@ function buildPayload(
|
|
|
254
301
|
schema: Payload,
|
|
255
302
|
body: undefined | BodyInit,
|
|
256
303
|
encodingHint?: string,
|
|
257
|
-
): null |
|
|
304
|
+
): null | { body: BodyInit; encoding: string } {
|
|
258
305
|
if (schema.encoding === undefined) {
|
|
259
306
|
if (body !== undefined) {
|
|
260
307
|
throw new TypeError(
|