@atproto/lex-client 0.0.9 → 0.0.11
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 +30 -0
- package/LICENSE.txt +1 -1
- package/dist/agent.d.ts +5 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -1
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +59 -40
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -6
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +52 -51
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +90 -71
- package/dist/errors.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +20 -10
- package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/defs.defs.d.ts +1 -1
- package/dist/lexicons/com/atproto/repo/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +14 -6
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +12 -4
- package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +11 -11
- package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/listRecords.defs.js +2 -1
- package/dist/lexicons/com/atproto/repo/listRecords.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +18 -10
- package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts.map +1 -1
- package/dist/response.d.ts +14 -13
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +36 -35
- package/dist/response.js.map +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js.map +1 -1
- package/dist/www-authenticate.d.ts +12 -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 +14 -21
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +18 -35
- package/dist/xrpc.js.map +1 -1
- package/package.json +6 -6
- package/src/agent.ts +34 -1
- package/src/client.ts +34 -33
- package/src/errors.ts +161 -128
- package/src/lexicons/com/atproto/repo/listRecords.defs.ts +4 -1
- package/src/response.ts +71 -71
- package/src/util.ts +1 -1
- package/src/www-authenticate.test.ts +227 -0
- package/src/www-authenticate.ts +77 -0
- package/src/xrpc.ts +53 -95
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { parseWWWAuthenticateHeader } from './www-authenticate.js'
|
|
3
|
+
|
|
4
|
+
describe(parseWWWAuthenticateHeader, () => {
|
|
5
|
+
describe('auth-params', () => {
|
|
6
|
+
it('parses single unquoted auth param', () => {
|
|
7
|
+
expect(parseWWWAuthenticateHeader('Bearer realm=example.com')).toEqual({
|
|
8
|
+
Bearer: { realm: 'example.com' },
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('parses single quoted auth param', () => {
|
|
13
|
+
expect(parseWWWAuthenticateHeader('Bearer realm="example.com"')).toEqual({
|
|
14
|
+
Bearer: { realm: 'example.com' },
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('parses quoted values with spaces', () => {
|
|
19
|
+
expect(parseWWWAuthenticateHeader('Bearer realm="my realm"')).toEqual({
|
|
20
|
+
Bearer: { realm: 'my realm' },
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('parses quoted values with escaped double quotes', () => {
|
|
25
|
+
expect(
|
|
26
|
+
parseWWWAuthenticateHeader('Bearer realm="example\\"quoted\\""'),
|
|
27
|
+
).toEqual({
|
|
28
|
+
Bearer: { realm: 'example"quoted"' },
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('parses quoted values with escaped backslash', () => {
|
|
33
|
+
expect(
|
|
34
|
+
parseWWWAuthenticateHeader('Bearer realm="path\\\\to\\\\file"'),
|
|
35
|
+
).toEqual({
|
|
36
|
+
Bearer: { realm: 'path\\to\\file' },
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('parses param names with hyphens', () => {
|
|
41
|
+
expect(
|
|
42
|
+
parseWWWAuthenticateHeader('Bearer error-uri="https://example.com"'),
|
|
43
|
+
).toEqual({
|
|
44
|
+
Bearer: { 'error-uri': 'https://example.com' },
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('parses param names with underscores', () => {
|
|
49
|
+
expect(
|
|
50
|
+
parseWWWAuthenticateHeader('Bearer error_description="test"'),
|
|
51
|
+
).toEqual({
|
|
52
|
+
Bearer: { error_description: 'test' },
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('parses param with numeric value', () => {
|
|
57
|
+
expect(parseWWWAuthenticateHeader('Bearer max-age=3600')).toEqual({
|
|
58
|
+
Bearer: { 'max-age': '3600' },
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('parses empty quoted value', () => {
|
|
63
|
+
expect(parseWWWAuthenticateHeader('Bearer realm=""')).toEqual({
|
|
64
|
+
Bearer: { realm: '' },
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('parses Basic auth challenge', () => {
|
|
69
|
+
expect(
|
|
70
|
+
parseWWWAuthenticateHeader('Basic realm="Access to staging site"'),
|
|
71
|
+
).toEqual({
|
|
72
|
+
Basic: { realm: 'Access to staging site' },
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('parses Bearer with realm', () => {
|
|
77
|
+
expect(
|
|
78
|
+
parseWWWAuthenticateHeader('Bearer realm="https://auth.example.com"'),
|
|
79
|
+
).toEqual({
|
|
80
|
+
Bearer: { realm: 'https://auth.example.com' },
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('parses DPoP with algs param', () => {
|
|
85
|
+
expect(parseWWWAuthenticateHeader('DPoP algs="ES256 RS256"')).toEqual({
|
|
86
|
+
DPoP: { algs: 'ES256 RS256' },
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('parses Digest auth challenge', () => {
|
|
91
|
+
const result = parseWWWAuthenticateHeader(
|
|
92
|
+
'Digest realm="digest-realm", nonce="abc123"',
|
|
93
|
+
)
|
|
94
|
+
expect(result).toEqual({
|
|
95
|
+
Digest: { realm: 'digest-realm', nonce: 'abc123' },
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('handle empty unquoted params', () => {
|
|
100
|
+
const result = parseWWWAuthenticateHeader('Bearer realm=')
|
|
101
|
+
expect(result).toEqual({ Bearer: { realm: '' } })
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('handle empty params', () => {
|
|
105
|
+
const result = parseWWWAuthenticateHeader('Bearer realm=""')
|
|
106
|
+
expect(result).toEqual({ Bearer: { realm: '' } })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('treats scheme-only header as scheme with itself as token68', () => {
|
|
110
|
+
const result = parseWWWAuthenticateHeader('Basic')
|
|
111
|
+
expect(result).toEqual({ Basic: {} })
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('parses multiple challenges with commas and escaped quotes', () => {
|
|
115
|
+
const result = parseWWWAuthenticateHeader(
|
|
116
|
+
`Newauth realm="apps", type=1,\n\t title="Login to \\"apps\\"", Basic realm="simple"`,
|
|
117
|
+
)
|
|
118
|
+
expect(result).toEqual({
|
|
119
|
+
Newauth: {
|
|
120
|
+
realm: 'apps',
|
|
121
|
+
type: '1',
|
|
122
|
+
title: 'Login to "apps"',
|
|
123
|
+
},
|
|
124
|
+
Basic: { realm: 'simple' },
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('parses first challenge before comma', () => {
|
|
129
|
+
const result = parseWWWAuthenticateHeader(
|
|
130
|
+
'Basic realm="foo", Bearer realm="bar"',
|
|
131
|
+
)
|
|
132
|
+
expect(result).toEqual({
|
|
133
|
+
Basic: { realm: 'foo' },
|
|
134
|
+
Bearer: { realm: 'bar' },
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('edge cases', () => {
|
|
140
|
+
it('handles empty string', () => {
|
|
141
|
+
expect(parseWWWAuthenticateHeader('')).toEqual({})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('handles whitespace-only string', () => {
|
|
145
|
+
expect(parseWWWAuthenticateHeader(' ')).toEqual({})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('trims whitespace from header', () => {
|
|
149
|
+
expect(parseWWWAuthenticateHeader(' Bearer realm="test" ')).toEqual({
|
|
150
|
+
Bearer: { realm: 'test' },
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('handles commas as quoted param value', () => {
|
|
155
|
+
expect(
|
|
156
|
+
parseWWWAuthenticateHeader('Bearer realm="example, with, commas"'),
|
|
157
|
+
).toEqual({ Bearer: { realm: 'example, with, commas' } })
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('handles multiple challenges with varying whitespace', () => {
|
|
161
|
+
expect(
|
|
162
|
+
parseWWWAuthenticateHeader(
|
|
163
|
+
' Bearer realm="test" , Basic rr= ',
|
|
164
|
+
),
|
|
165
|
+
).toEqual({
|
|
166
|
+
Bearer: { realm: 'test' },
|
|
167
|
+
Basic: { rr: '' },
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
describe('invalid challenges', () => {
|
|
173
|
+
it('parses single challenge with no comma correctly', () => {
|
|
174
|
+
expect(
|
|
175
|
+
parseWWWAuthenticateHeader('Bearer realm="oauth" error="invalid"'),
|
|
176
|
+
).toEqual(undefined)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('ignores invalid challenges', () => {
|
|
180
|
+
expect(parseWWWAuthenticateHeader('Bearer realm="unclosed')).toEqual(
|
|
181
|
+
undefined,
|
|
182
|
+
)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('handles random text without equals sign as token68', () => {
|
|
186
|
+
expect(parseWWWAuthenticateHeader('Bearer sometoken')).toEqual({
|
|
187
|
+
Bearer: 'sometoken',
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('ignores trailing whitespace after scheme', () => {
|
|
192
|
+
expect(parseWWWAuthenticateHeader('Bearer ')).toEqual({ Bearer: {} })
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('handles duplicate params (last wins)', () => {
|
|
196
|
+
expect(
|
|
197
|
+
parseWWWAuthenticateHeader('Bearer realm="first", realm="second"'),
|
|
198
|
+
).toEqual({ Bearer: { realm: 'second' } })
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('extracts valid param after invalid characters', () => {
|
|
202
|
+
expect(parseWWWAuthenticateHeader('Bearer realm@foo="bar"')).toEqual({
|
|
203
|
+
Bearer: { 'realm@foo': 'bar' },
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('ignores param with empty name', () => {
|
|
208
|
+
expect(parseWWWAuthenticateHeader('Bearer ="value"')).toEqual(undefined)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('handles completely malformed input gracefully', () => {
|
|
212
|
+
expect(parseWWWAuthenticateHeader('!@#$%')).toEqual({ '!@#$%': {} })
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('handles duplicate schemes as invalid', () => {
|
|
216
|
+
expect(
|
|
217
|
+
parseWWWAuthenticateHeader('Basic realm="first", Basic realm="second"'),
|
|
218
|
+
).toEqual(undefined)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('handles params without scheme as invalid', () => {
|
|
222
|
+
expect(parseWWWAuthenticateHeader('Bearer, realm="foo"')).toEqual(
|
|
223
|
+
undefined,
|
|
224
|
+
)
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
type WWWAuthenticateParams = { [authParam in string]: string }
|
|
2
|
+
export type WWWAuthenticate = {
|
|
3
|
+
[authScheme in string]:
|
|
4
|
+
| string // token68
|
|
5
|
+
| WWWAuthenticateParams
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns `undefined` if the header is malformed.
|
|
10
|
+
*/
|
|
11
|
+
export function parseWWWAuthenticateHeader(
|
|
12
|
+
header?: unknown,
|
|
13
|
+
): undefined | WWWAuthenticate {
|
|
14
|
+
if (typeof header !== 'string') return undefined
|
|
15
|
+
|
|
16
|
+
const wwwAuthenticate: WWWAuthenticate = {}
|
|
17
|
+
|
|
18
|
+
// Split over commas, not within quoted strings
|
|
19
|
+
const trimmedHeader = header.trim()
|
|
20
|
+
if (!trimmedHeader) return wwwAuthenticate
|
|
21
|
+
|
|
22
|
+
const parts = trimmedHeader.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/)
|
|
23
|
+
|
|
24
|
+
let currentParams: WWWAuthenticateParams | null = null
|
|
25
|
+
|
|
26
|
+
for (let part of parts) {
|
|
27
|
+
// Check if the part starts with an auth scheme
|
|
28
|
+
const schemeMatch = part.trim().match(/^([^"=\s]+)(\s+.*)?$/)
|
|
29
|
+
if (schemeMatch) {
|
|
30
|
+
const scheme = schemeMatch[1]
|
|
31
|
+
|
|
32
|
+
// Duplicate scheme
|
|
33
|
+
if (Object.hasOwn(wwwAuthenticate, scheme)) return undefined
|
|
34
|
+
|
|
35
|
+
const rest = schemeMatch[2]?.trim()
|
|
36
|
+
if (!rest) {
|
|
37
|
+
// Scheme only (no params or token68)
|
|
38
|
+
currentParams = null
|
|
39
|
+
wwwAuthenticate[scheme] = Object.create(null)
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!rest.includes('=')) {
|
|
44
|
+
// Scheme with token68
|
|
45
|
+
currentParams = null
|
|
46
|
+
wwwAuthenticate[scheme] = rest
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Scheme with params
|
|
51
|
+
|
|
52
|
+
currentParams = Object.create(null) as WWWAuthenticateParams
|
|
53
|
+
wwwAuthenticate[scheme] = currentParams
|
|
54
|
+
|
|
55
|
+
// Fall through to parse params
|
|
56
|
+
part = rest
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Invalid header
|
|
60
|
+
if (!currentParams) return undefined
|
|
61
|
+
|
|
62
|
+
const param = part.match(
|
|
63
|
+
/^\s*([^"\s=]+)=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([^\s,"]*))\s*$/,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// invalid param
|
|
67
|
+
if (!param) return undefined
|
|
68
|
+
|
|
69
|
+
const paramName = param[1]
|
|
70
|
+
const paramValue =
|
|
71
|
+
param[3] ?? param[2]!.slice(1, -1).replaceAll(/\\(.)/g, '$1')
|
|
72
|
+
|
|
73
|
+
currentParams[paramName] = paramValue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return wwwAuthenticate
|
|
77
|
+
}
|
package/src/xrpc.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { LexValue, isLexScalar, isPlainObject } from '@atproto/lex-data'
|
|
2
2
|
import { lexStringify } from '@atproto/lex-json'
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
InferInput,
|
|
5
|
+
InferPayload,
|
|
6
6
|
Main,
|
|
7
7
|
Params,
|
|
8
|
-
|
|
9
|
-
Payload as LexPayload,
|
|
8
|
+
Payload,
|
|
10
9
|
Procedure,
|
|
11
10
|
Query,
|
|
12
11
|
Restricted,
|
|
@@ -14,15 +13,11 @@ import {
|
|
|
14
13
|
getMain,
|
|
15
14
|
} from '@atproto/lex-schema'
|
|
16
15
|
import { Agent } from './agent.js'
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
LexRpcUnexpectedError,
|
|
20
|
-
LexRpcUpstreamError,
|
|
21
|
-
} from './errors.js'
|
|
22
|
-
import { LexRpcResponse } from './response.js'
|
|
16
|
+
import { XrpcFailure, asXrpcFailure } from './errors.js'
|
|
17
|
+
import { XrpcResponse } from './response.js'
|
|
23
18
|
import { BinaryBodyInit, CallOptions } from './types.js'
|
|
24
19
|
import {
|
|
25
|
-
|
|
20
|
+
XrpcPayload,
|
|
26
21
|
buildAtprotoHeaders,
|
|
27
22
|
isAsyncIterable,
|
|
28
23
|
isBlobLike,
|
|
@@ -30,106 +25,80 @@ import {
|
|
|
30
25
|
} from './util.js'
|
|
31
26
|
|
|
32
27
|
// If all params are optional, allow omitting the params object
|
|
33
|
-
type
|
|
28
|
+
type XrpcParamsOptions<P extends Params> =
|
|
34
29
|
NonNullable<unknown> extends P ? { params?: P } : { params: P }
|
|
35
30
|
|
|
36
|
-
type
|
|
37
|
-
M
|
|
38
|
-
BinaryBodyInit
|
|
39
|
-
>
|
|
31
|
+
export type XrpcRequestParams<M extends Procedure | Query | Subscription> =
|
|
32
|
+
InferInput<M['parameters']>
|
|
40
33
|
|
|
41
|
-
type
|
|
34
|
+
type XrpcRequestPayload<M extends Procedure | Query> = M extends Procedure
|
|
35
|
+
? InferPayload<M['input'], BinaryBodyInit>
|
|
36
|
+
: undefined
|
|
37
|
+
|
|
38
|
+
type XrpcInputOptions<In> = In extends { body: infer B; encoding: infer E }
|
|
42
39
|
? // encoding will be inferred from the schema at runtime if not provided
|
|
43
40
|
{ body: B; encoding?: E }
|
|
44
41
|
: { body?: undefined; encoding?: undefined }
|
|
45
42
|
|
|
46
|
-
export type
|
|
43
|
+
export type XrpcOptions<M extends Procedure | Query = Procedure | Query> =
|
|
47
44
|
CallOptions &
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
export type LexRpcFailure<M extends Procedure | Query> =
|
|
52
|
-
// The server returned a valid XRPC error response
|
|
53
|
-
| LexRpcResponseError<M>
|
|
54
|
-
// The response was not a valid XRPC response, or it does not match the schema
|
|
55
|
-
| LexRpcUpstreamError
|
|
56
|
-
// Something went wrong (network error, etc.)
|
|
57
|
-
| LexRpcUnexpectedError
|
|
58
|
-
|
|
59
|
-
export type LexRpcResult<M extends Procedure | Query> =
|
|
60
|
-
| LexRpcResponse<M>
|
|
61
|
-
| LexRpcFailure<M>
|
|
45
|
+
XrpcInputOptions<XrpcRequestPayload<M>> &
|
|
46
|
+
XrpcParamsOptions<XrpcRequestParams<M>>
|
|
62
47
|
|
|
63
48
|
/**
|
|
64
|
-
*
|
|
65
|
-
* {@link LexRpcFailure} matching the provided method. Only use this function
|
|
66
|
-
* inside a catch block right after calling {@link xrpc}, and use the same
|
|
67
|
-
* method type parameter as used in the {@link xrpc} call.
|
|
68
|
-
*/
|
|
69
|
-
export function asLexRpcFailure<
|
|
70
|
-
M extends Procedure | Query = Procedure | Query,
|
|
71
|
-
>(err: unknown): LexRpcFailure<M> {
|
|
72
|
-
if (err instanceof LexRpcResponseError) return err
|
|
73
|
-
if (err instanceof LexRpcUpstreamError) return err
|
|
74
|
-
return LexRpcUnexpectedError.from(err)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @throws LexRpcFailure<M>
|
|
49
|
+
* @throws XrpcFailure<M>
|
|
79
50
|
*/
|
|
80
51
|
export async function xrpc<const M extends Query | Procedure>(
|
|
81
52
|
agent: Agent,
|
|
82
|
-
ns: NonNullable<unknown> extends
|
|
53
|
+
ns: NonNullable<unknown> extends XrpcOptions<M>
|
|
83
54
|
? Main<M>
|
|
84
55
|
: Restricted<'This XRPC method requires an "options" argument'>,
|
|
85
|
-
): Promise<
|
|
56
|
+
): Promise<XrpcResponse<M>>
|
|
86
57
|
export async function xrpc<const M extends Query | Procedure>(
|
|
87
58
|
agent: Agent,
|
|
88
59
|
ns: Main<M>,
|
|
89
|
-
options:
|
|
90
|
-
): Promise<
|
|
60
|
+
options: XrpcOptions<M>,
|
|
61
|
+
): Promise<XrpcResponse<M>>
|
|
91
62
|
export async function xrpc<const M extends Query | Procedure>(
|
|
92
63
|
agent: Agent,
|
|
93
64
|
ns: Main<M>,
|
|
94
|
-
options:
|
|
95
|
-
): Promise<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
throw asLexRpcFailure<M>(err)
|
|
100
|
-
}
|
|
65
|
+
options: XrpcOptions<M> = {} as XrpcOptions<M>,
|
|
66
|
+
): Promise<XrpcResponse<M>> {
|
|
67
|
+
const response = await xrpcSafe<M>(agent, ns, options)
|
|
68
|
+
if (response.success) return response
|
|
69
|
+
else throw response
|
|
101
70
|
}
|
|
102
71
|
|
|
72
|
+
export type XrpcResult<M extends Procedure | Query> =
|
|
73
|
+
| XrpcResponse<M>
|
|
74
|
+
| XrpcFailure<M>
|
|
75
|
+
|
|
103
76
|
export async function xrpcSafe<const M extends Query | Procedure>(
|
|
104
77
|
agent: Agent,
|
|
105
|
-
ns: NonNullable<unknown> extends
|
|
78
|
+
ns: NonNullable<unknown> extends XrpcOptions<M>
|
|
106
79
|
? Main<M>
|
|
107
80
|
: Restricted<'This XRPC method requires an "options" argument'>,
|
|
108
|
-
): Promise<
|
|
81
|
+
): Promise<XrpcResult<M>>
|
|
109
82
|
export async function xrpcSafe<const M extends Query | Procedure>(
|
|
110
83
|
agent: Agent,
|
|
111
84
|
ns: Main<M>,
|
|
112
|
-
options:
|
|
113
|
-
): Promise<
|
|
85
|
+
options: XrpcOptions<M>,
|
|
86
|
+
): Promise<XrpcResult<M>>
|
|
114
87
|
export async function xrpcSafe<const M extends Query | Procedure>(
|
|
115
88
|
agent: Agent,
|
|
116
89
|
ns: Main<M>,
|
|
117
|
-
options:
|
|
118
|
-
): Promise<
|
|
119
|
-
return lexRpcRequest<M>(agent, ns, options).catch(asLexRpcFailure<M>)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function lexRpcRequest<const M extends Query | Procedure>(
|
|
123
|
-
agent: Agent,
|
|
124
|
-
ns: Main<M>,
|
|
125
|
-
options: LexRpcOptions<M> = {} as LexRpcOptions<M>,
|
|
126
|
-
): Promise<LexRpcResponse<M>> {
|
|
127
|
-
const method = getMain(ns)
|
|
90
|
+
options: XrpcOptions<M> = {} as XrpcOptions<M>,
|
|
91
|
+
): Promise<XrpcResult<M>> {
|
|
128
92
|
options.signal?.throwIfAborted()
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
93
|
+
const method: M = getMain(ns)
|
|
94
|
+
try {
|
|
95
|
+
const url = xrpcRequestUrl(method, options)
|
|
96
|
+
const request = xrpcRequestInit(method, options)
|
|
97
|
+
const response = await agent.fetchHandler(url, request)
|
|
98
|
+
return await XrpcResponse.fromFetchResponse<M>(method, response, options)
|
|
99
|
+
} catch (cause) {
|
|
100
|
+
return asXrpcFailure(method, cause)
|
|
101
|
+
}
|
|
133
102
|
}
|
|
134
103
|
|
|
135
104
|
function xrpcRequestUrl<M extends Procedure | Query | Subscription>(
|
|
@@ -138,24 +107,13 @@ function xrpcRequestUrl<M extends Procedure | Query | Subscription>(
|
|
|
138
107
|
) {
|
|
139
108
|
const path = `/xrpc/${method.nsid}`
|
|
140
109
|
|
|
141
|
-
const queryString =
|
|
142
|
-
|
|
143
|
-
|
|
110
|
+
const queryString = method.parameters
|
|
111
|
+
?.toURLSearchParams(options.params ?? {})
|
|
112
|
+
.toString()
|
|
144
113
|
|
|
145
114
|
return queryString ? `${path}?${queryString}` : path
|
|
146
115
|
}
|
|
147
116
|
|
|
148
|
-
function xrpcRequestParams(
|
|
149
|
-
schema: ParamsSchema | undefined,
|
|
150
|
-
params: Params | undefined,
|
|
151
|
-
options: CallOptions,
|
|
152
|
-
): undefined | string {
|
|
153
|
-
const urlSearchParams = schema?.toURLSearchParams(
|
|
154
|
-
options.validateRequest ? schema.parse(params) : (params as any),
|
|
155
|
-
)
|
|
156
|
-
return urlSearchParams?.size ? urlSearchParams.toString() : undefined
|
|
157
|
-
}
|
|
158
|
-
|
|
159
117
|
function xrpcRequestInit<T extends Procedure | Query>(
|
|
160
118
|
schema: T,
|
|
161
119
|
options: CallOptions & {
|
|
@@ -215,7 +173,7 @@ function xrpcProcedureInput(
|
|
|
215
173
|
method: Procedure,
|
|
216
174
|
options: CallOptions & { body?: LexValue | BinaryBodyInit },
|
|
217
175
|
encodingHint?: string,
|
|
218
|
-
): null |
|
|
176
|
+
): null | XrpcPayload<BodyInit> {
|
|
219
177
|
const { input } = method
|
|
220
178
|
const { body } = options
|
|
221
179
|
|
|
@@ -261,10 +219,10 @@ function xrpcProcedureInput(
|
|
|
261
219
|
}
|
|
262
220
|
|
|
263
221
|
function buildPayload(
|
|
264
|
-
schema:
|
|
222
|
+
schema: Payload,
|
|
265
223
|
body: undefined | BodyInit,
|
|
266
224
|
encodingHint?: string,
|
|
267
|
-
): null |
|
|
225
|
+
): null | XrpcPayload<BodyInit> {
|
|
268
226
|
if (schema.encoding === undefined) {
|
|
269
227
|
if (body !== undefined) {
|
|
270
228
|
throw new TypeError(
|
|
@@ -286,7 +244,7 @@ function buildPayload(
|
|
|
286
244
|
return { encoding, body }
|
|
287
245
|
}
|
|
288
246
|
|
|
289
|
-
function buildEncoding(schema:
|
|
247
|
+
function buildEncoding(schema: Payload, encodingHint?: string): string {
|
|
290
248
|
// Should never happen (required for type safety)
|
|
291
249
|
if (!schema.encoding) {
|
|
292
250
|
throw new TypeError('Unexpected payload')
|