@atproto/xrpc-server 0.11.4 → 0.11.6
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 +27 -0
- package/package.json +26 -21
- package/jest.config.cjs +0 -21
- package/src/auth.ts +0 -235
- package/src/errors.ts +0 -312
- package/src/index.ts +0 -14
- package/src/logger.ts +0 -8
- package/src/rate-limiter-http.ts +0 -82
- package/src/rate-limiter.ts +0 -279
- package/src/server.ts +0 -858
- package/src/stream/frames.ts +0 -125
- package/src/stream/index.ts +0 -5
- package/src/stream/logger.ts +0 -6
- package/src/stream/server.ts +0 -66
- package/src/stream/stream.ts +0 -39
- package/src/stream/subscription.ts +0 -96
- package/src/stream/types.ts +0 -27
- package/src/types.ts +0 -330
- package/src/util.ts +0 -708
- package/tests/_util.ts +0 -124
- package/tests/auth.test.ts +0 -333
- package/tests/bodies.test.ts +0 -608
- package/tests/errors.test.ts +0 -299
- package/tests/frames.test.ts +0 -135
- package/tests/ipld.test.ts +0 -97
- package/tests/parameters.test.ts +0 -331
- package/tests/parsing.test.ts +0 -89
- package/tests/procedures.test.ts +0 -176
- package/tests/queries.test.ts +0 -140
- package/tests/rate-limiter.test.ts +0 -312
- package/tests/responses.test.ts +0 -72
- package/tests/stream.test.ts +0 -169
- package/tests/subscriptions.test.ts +0 -398
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/tests/errors.test.ts
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import * as http from 'node:http'
|
|
2
|
-
import { AddressInfo } from 'node:net'
|
|
3
|
-
import { LexiconDoc } from '@atproto/lexicon'
|
|
4
|
-
import { XRPCError, XRPCInvalidResponseError, XrpcClient } from '@atproto/xrpc'
|
|
5
|
-
import * as xrpcServer from '../src/index.js'
|
|
6
|
-
import { closeServer, createServer } from './_util.js'
|
|
7
|
-
|
|
8
|
-
const UPSTREAM_LEXICONS: LexiconDoc[] = [
|
|
9
|
-
{
|
|
10
|
-
lexicon: 1,
|
|
11
|
-
id: 'io.example.upstreamInvalidResponse',
|
|
12
|
-
defs: {
|
|
13
|
-
main: {
|
|
14
|
-
type: 'query',
|
|
15
|
-
output: {
|
|
16
|
-
encoding: 'application/json',
|
|
17
|
-
schema: {
|
|
18
|
-
type: 'object',
|
|
19
|
-
required: ['expectedValue'],
|
|
20
|
-
properties: {
|
|
21
|
-
expectedValue: { type: 'string' },
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
const LEXICONS: LexiconDoc[] = [
|
|
31
|
-
{
|
|
32
|
-
lexicon: 1,
|
|
33
|
-
id: 'io.example.error',
|
|
34
|
-
defs: {
|
|
35
|
-
main: {
|
|
36
|
-
type: 'query',
|
|
37
|
-
parameters: {
|
|
38
|
-
type: 'params',
|
|
39
|
-
properties: {
|
|
40
|
-
which: { type: 'string', default: 'foo' },
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
errors: [{ name: 'Foo' }, { name: 'Bar' }],
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
lexicon: 1,
|
|
49
|
-
id: 'io.example.throwFalsyValue',
|
|
50
|
-
defs: {
|
|
51
|
-
main: {
|
|
52
|
-
type: 'query',
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
lexicon: 1,
|
|
58
|
-
id: 'io.example.query',
|
|
59
|
-
defs: {
|
|
60
|
-
main: {
|
|
61
|
-
type: 'query',
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
lexicon: 1,
|
|
67
|
-
id: 'io.example.procedure',
|
|
68
|
-
defs: {
|
|
69
|
-
main: {
|
|
70
|
-
type: 'procedure',
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
lexicon: 1,
|
|
76
|
-
id: 'io.example.invalidResponse',
|
|
77
|
-
defs: {
|
|
78
|
-
main: {
|
|
79
|
-
type: 'query',
|
|
80
|
-
output: {
|
|
81
|
-
encoding: 'application/json',
|
|
82
|
-
schema: {
|
|
83
|
-
type: 'object',
|
|
84
|
-
required: ['expectedValue'],
|
|
85
|
-
properties: {
|
|
86
|
-
expectedValue: { type: 'string' },
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
lexicon: 1,
|
|
95
|
-
id: 'io.example.invalidUpstreamResponse',
|
|
96
|
-
defs: {
|
|
97
|
-
main: {
|
|
98
|
-
type: 'query',
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
]
|
|
103
|
-
|
|
104
|
-
const MISMATCHED_LEXICONS: LexiconDoc[] = [
|
|
105
|
-
{
|
|
106
|
-
lexicon: 1,
|
|
107
|
-
id: 'io.example.query',
|
|
108
|
-
defs: {
|
|
109
|
-
main: {
|
|
110
|
-
type: 'procedure',
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
lexicon: 1,
|
|
116
|
-
id: 'io.example.procedure',
|
|
117
|
-
defs: {
|
|
118
|
-
main: {
|
|
119
|
-
type: 'query',
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
lexicon: 1,
|
|
125
|
-
id: 'io.example.doesNotExist',
|
|
126
|
-
defs: {
|
|
127
|
-
main: {
|
|
128
|
-
type: 'query',
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
]
|
|
133
|
-
|
|
134
|
-
describe('Errors', () => {
|
|
135
|
-
let upstreamS: http.Server
|
|
136
|
-
const upstreamServer = xrpcServer.createServer(UPSTREAM_LEXICONS, {
|
|
137
|
-
validateResponse: false,
|
|
138
|
-
}) // disable validateResponse to test client validation
|
|
139
|
-
upstreamServer.method('io.example.upstreamInvalidResponse', () => {
|
|
140
|
-
return { encoding: 'json', body: { something: 'else' } }
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
let upstreamClient: XrpcClient
|
|
144
|
-
|
|
145
|
-
let s: http.Server
|
|
146
|
-
const server = xrpcServer.createServer(LEXICONS, { validateResponse: false }) // disable validateResponse to test client validation
|
|
147
|
-
server.method('io.example.error', (ctx) => {
|
|
148
|
-
if (ctx.params.which === 'foo') {
|
|
149
|
-
throw new xrpcServer.InvalidRequestError('It was this one!', 'Foo')
|
|
150
|
-
} else if (ctx.params.which === 'bar') {
|
|
151
|
-
return { status: 400, error: 'Bar', message: 'It was that one!' }
|
|
152
|
-
} else {
|
|
153
|
-
return { status: 400 }
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
server.method('io.example.throwFalsyValue', () => {
|
|
157
|
-
throw ''
|
|
158
|
-
})
|
|
159
|
-
server.method('io.example.query', () => {
|
|
160
|
-
return undefined
|
|
161
|
-
})
|
|
162
|
-
// @ts-ignore We're intentionally giving the wrong response! -prf
|
|
163
|
-
server.method('io.example.invalidResponse', () => {
|
|
164
|
-
return { encoding: 'json', body: { something: 'else' } }
|
|
165
|
-
})
|
|
166
|
-
server.method('io.example.invalidUpstreamResponse', async () => {
|
|
167
|
-
await upstreamClient.call('io.example.upstreamInvalidResponse')
|
|
168
|
-
return {
|
|
169
|
-
encoding: 'json',
|
|
170
|
-
}
|
|
171
|
-
})
|
|
172
|
-
server.method('io.example.procedure', () => {
|
|
173
|
-
return undefined
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
let client: XrpcClient
|
|
177
|
-
let badClient: XrpcClient
|
|
178
|
-
beforeAll(async () => {
|
|
179
|
-
upstreamS = await createServer(upstreamServer)
|
|
180
|
-
const { port: upstreamPort } = upstreamS.address() as AddressInfo
|
|
181
|
-
upstreamClient = new XrpcClient(
|
|
182
|
-
`http://localhost:${upstreamPort}`,
|
|
183
|
-
UPSTREAM_LEXICONS,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
s = await createServer(server)
|
|
187
|
-
const { port } = s.address() as AddressInfo
|
|
188
|
-
client = new XrpcClient(`http://localhost:${port}`, LEXICONS)
|
|
189
|
-
badClient = new XrpcClient(`http://localhost:${port}`, MISMATCHED_LEXICONS)
|
|
190
|
-
})
|
|
191
|
-
afterAll(async () => {
|
|
192
|
-
await closeServer(s)
|
|
193
|
-
await closeServer(upstreamS)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('serves requests', async () => {
|
|
197
|
-
try {
|
|
198
|
-
await client.call('io.example.error', {
|
|
199
|
-
which: 'foo',
|
|
200
|
-
})
|
|
201
|
-
throw new Error('Didnt throw')
|
|
202
|
-
} catch (e) {
|
|
203
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
204
|
-
expect((e as XRPCError).success).toBeFalsy()
|
|
205
|
-
expect((e as XRPCError).error).toBe('Foo')
|
|
206
|
-
expect((e as XRPCError).message).toBe('It was this one!')
|
|
207
|
-
}
|
|
208
|
-
try {
|
|
209
|
-
await client.call('io.example.error', {
|
|
210
|
-
which: 'bar',
|
|
211
|
-
})
|
|
212
|
-
throw new Error('Didnt throw')
|
|
213
|
-
} catch (e) {
|
|
214
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
215
|
-
expect((e as XRPCError).success).toBeFalsy()
|
|
216
|
-
expect((e as XRPCError).error).toBe('Bar')
|
|
217
|
-
expect((e as XRPCError).message).toBe('It was that one!')
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
await client.call('io.example.throwFalsyValue')
|
|
221
|
-
throw new Error('Didnt throw')
|
|
222
|
-
} catch (e) {
|
|
223
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
224
|
-
expect((e as XRPCError).success).toBeFalsy()
|
|
225
|
-
expect((e as XRPCError).error).toBe('InternalServerError')
|
|
226
|
-
expect((e as XRPCError).message).toBe('Internal Server Error')
|
|
227
|
-
}
|
|
228
|
-
try {
|
|
229
|
-
await client.call('io.example.error', {
|
|
230
|
-
which: 'other',
|
|
231
|
-
})
|
|
232
|
-
throw new Error('Didnt throw')
|
|
233
|
-
} catch (e) {
|
|
234
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
235
|
-
expect((e as XRPCError).success).toBeFalsy()
|
|
236
|
-
expect((e as XRPCError).error).toBe('InvalidRequest')
|
|
237
|
-
expect((e as XRPCError).message).toBe('Invalid Request')
|
|
238
|
-
}
|
|
239
|
-
try {
|
|
240
|
-
await client.call('io.example.invalidResponse')
|
|
241
|
-
throw new Error('Didnt throw')
|
|
242
|
-
} catch (e: any) {
|
|
243
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
244
|
-
expect(e).toBeInstanceOf(XRPCInvalidResponseError)
|
|
245
|
-
expect(e.success).toBeFalsy()
|
|
246
|
-
expect(e.error).toBe('Invalid Response')
|
|
247
|
-
expect(e.message).toBe(
|
|
248
|
-
'The server gave an invalid response and may be out of date.',
|
|
249
|
-
)
|
|
250
|
-
const err = e as XRPCInvalidResponseError
|
|
251
|
-
expect(err.validationError.message).toBe(
|
|
252
|
-
'Output must have the property "expectedValue"',
|
|
253
|
-
)
|
|
254
|
-
expect(err.responseBody).toStrictEqual({ something: 'else' })
|
|
255
|
-
}
|
|
256
|
-
try {
|
|
257
|
-
await client.call('io.example.invalidUpstreamResponse')
|
|
258
|
-
throw new Error('Didnt throw')
|
|
259
|
-
} catch (e: any) {
|
|
260
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
261
|
-
expect((e as XRPCError).status).toBe(500)
|
|
262
|
-
expect((e as XRPCError).success).toBeFalsy()
|
|
263
|
-
expect((e as XRPCError).error).toBe('InternalServerError')
|
|
264
|
-
expect((e as XRPCError).message).toBe('Internal Server Error')
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
it('serves error for missing/mismatch schemas', async () => {
|
|
269
|
-
await client.call('io.example.query') // No error
|
|
270
|
-
await client.call('io.example.procedure') // No error
|
|
271
|
-
try {
|
|
272
|
-
await badClient.call('io.example.query')
|
|
273
|
-
throw new Error('Didnt throw')
|
|
274
|
-
} catch (e: any) {
|
|
275
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
276
|
-
expect(e.success).toBeFalsy()
|
|
277
|
-
expect(e.error).toBe('InvalidRequest')
|
|
278
|
-
expect(e.message).toBe('Incorrect HTTP method (POST) expected GET')
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
await badClient.call('io.example.procedure')
|
|
282
|
-
throw new Error('Didnt throw')
|
|
283
|
-
} catch (e: any) {
|
|
284
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
285
|
-
expect(e.success).toBeFalsy()
|
|
286
|
-
expect(e.error).toBe('InvalidRequest')
|
|
287
|
-
expect(e.message).toBe('Incorrect HTTP method (GET) expected POST')
|
|
288
|
-
}
|
|
289
|
-
try {
|
|
290
|
-
await badClient.call('io.example.doesNotExist')
|
|
291
|
-
throw new Error('Didnt throw')
|
|
292
|
-
} catch (e: any) {
|
|
293
|
-
expect(e).toBeInstanceOf(XRPCError)
|
|
294
|
-
expect(e.success).toBeFalsy()
|
|
295
|
-
expect(e.error).toBe('MethodNotImplemented')
|
|
296
|
-
expect(e.message).toBe('Method Not Implemented')
|
|
297
|
-
}
|
|
298
|
-
})
|
|
299
|
-
})
|
package/tests/frames.test.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { encode } from '@atproto/lex-cbor'
|
|
2
|
-
import { ui8Equals } from '@atproto/lex-data'
|
|
3
|
-
import { ErrorFrame, Frame, FrameType, MessageFrame } from '../src/index.js'
|
|
4
|
-
|
|
5
|
-
describe('Frames', () => {
|
|
6
|
-
it('creates and parses message frame.', async () => {
|
|
7
|
-
const messageFrame = new MessageFrame(
|
|
8
|
-
{ a: 'b', c: [1, 2, 3] },
|
|
9
|
-
{ type: '#d' },
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
expect(messageFrame.header).toEqual({
|
|
13
|
-
op: FrameType.Message,
|
|
14
|
-
t: '#d',
|
|
15
|
-
})
|
|
16
|
-
expect(messageFrame.op).toEqual(FrameType.Message)
|
|
17
|
-
expect(messageFrame.type).toEqual('#d')
|
|
18
|
-
expect(messageFrame.body).toEqual({ a: 'b', c: [1, 2, 3] })
|
|
19
|
-
|
|
20
|
-
const bytes = messageFrame.toBytes()
|
|
21
|
-
expect(
|
|
22
|
-
ui8Equals(
|
|
23
|
-
bytes,
|
|
24
|
-
new Uint8Array([
|
|
25
|
-
/*header*/ 162, 97, 116, 98, 35, 100, 98, 111, 112, 1, /*body*/ 162,
|
|
26
|
-
97, 97, 97, 98, 97, 99, 131, 1, 2, 3,
|
|
27
|
-
]),
|
|
28
|
-
),
|
|
29
|
-
).toEqual(true)
|
|
30
|
-
|
|
31
|
-
const parsedFrame = Frame.fromBytes(bytes)
|
|
32
|
-
if (!(parsedFrame instanceof MessageFrame)) {
|
|
33
|
-
throw new Error('Did not parse as message frame')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
expect(parsedFrame.header).toEqual(messageFrame.header)
|
|
37
|
-
expect(parsedFrame.op).toEqual(messageFrame.op)
|
|
38
|
-
expect(parsedFrame.type).toEqual(messageFrame.type)
|
|
39
|
-
expect(parsedFrame.body).toEqual(messageFrame.body)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('creates and parses error frame.', async () => {
|
|
43
|
-
const errorFrame = new ErrorFrame({
|
|
44
|
-
error: 'BigOops',
|
|
45
|
-
message: 'Something went awry',
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
expect(errorFrame.header).toEqual({ op: FrameType.Error })
|
|
49
|
-
expect(errorFrame.op).toEqual(FrameType.Error)
|
|
50
|
-
expect(errorFrame.code).toEqual('BigOops')
|
|
51
|
-
expect(errorFrame.message).toEqual('Something went awry')
|
|
52
|
-
expect(errorFrame.body).toEqual({
|
|
53
|
-
error: 'BigOops',
|
|
54
|
-
message: 'Something went awry',
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const bytes = errorFrame.toBytes()
|
|
58
|
-
expect(
|
|
59
|
-
ui8Equals(
|
|
60
|
-
bytes,
|
|
61
|
-
new Uint8Array([
|
|
62
|
-
/*header*/ 161, 98, 111, 112, 32, /*body*/ 162, 101, 101, 114, 114,
|
|
63
|
-
111, 114, 103, 66, 105, 103, 79, 111, 112, 115, 103, 109, 101, 115,
|
|
64
|
-
115, 97, 103, 101, 115, 83, 111, 109, 101, 116, 104, 105, 110, 103,
|
|
65
|
-
32, 119, 101, 110, 116, 32, 97, 119, 114, 121,
|
|
66
|
-
]),
|
|
67
|
-
),
|
|
68
|
-
).toEqual(true)
|
|
69
|
-
|
|
70
|
-
const parsedFrame = Frame.fromBytes(bytes)
|
|
71
|
-
if (!(parsedFrame instanceof ErrorFrame)) {
|
|
72
|
-
throw new Error('Did not parse as error frame')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
expect(parsedFrame.header).toEqual(errorFrame.header)
|
|
76
|
-
expect(parsedFrame.op).toEqual(errorFrame.op)
|
|
77
|
-
expect(parsedFrame.code).toEqual(errorFrame.code)
|
|
78
|
-
expect(parsedFrame.message).toEqual(errorFrame.message)
|
|
79
|
-
expect(parsedFrame.body).toEqual(errorFrame.body)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('parsing fails when frame is not CBOR.', async () => {
|
|
83
|
-
const bytes = Buffer.from('some utf8 bytes')
|
|
84
|
-
const emptyBytes = Buffer.alloc(0)
|
|
85
|
-
expect(() => Frame.fromBytes(bytes)).toThrow()
|
|
86
|
-
expect(() => Frame.fromBytes(emptyBytes)).toThrow()
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('parsing fails when frame header is malformed.', async () => {
|
|
90
|
-
const bytes = Buffer.concat([
|
|
91
|
-
encode({ op: -2 }), // Unknown op
|
|
92
|
-
encode({ a: 'b', c: [1, 2, 3] }),
|
|
93
|
-
])
|
|
94
|
-
|
|
95
|
-
expect(() => Frame.fromBytes(bytes)).toThrow('Invalid frame header:')
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('parsing fails when frame is missing body.', async () => {
|
|
99
|
-
const messageFrame = new MessageFrame(
|
|
100
|
-
{ a: 'b', c: [1, 2, 3] },
|
|
101
|
-
{ type: '#d' },
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
const headerBytes = encode(messageFrame.header)
|
|
105
|
-
|
|
106
|
-
expect(() => Frame.fromBytes(headerBytes)).toThrow('Missing frame body')
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('parsing fails when frame has too many data items.', async () => {
|
|
110
|
-
const messageFrame = new MessageFrame(
|
|
111
|
-
{ a: 'b', c: [1, 2, 3] },
|
|
112
|
-
{ type: '#d' },
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
const bytes = Buffer.concat([
|
|
116
|
-
messageFrame.toBytes(),
|
|
117
|
-
encode({ d: 'e', f: [4, 5, 6] }),
|
|
118
|
-
])
|
|
119
|
-
|
|
120
|
-
expect(() => Frame.fromBytes(bytes)).toThrow(
|
|
121
|
-
'Too many CBOR data items in frame',
|
|
122
|
-
)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('parsing fails when error frame has invalid body.', async () => {
|
|
126
|
-
const errorFrame = new ErrorFrame({ error: 'BadOops' })
|
|
127
|
-
|
|
128
|
-
const bytes = Buffer.concat([
|
|
129
|
-
encode(errorFrame.header),
|
|
130
|
-
encode({ blah: 1 }),
|
|
131
|
-
])
|
|
132
|
-
|
|
133
|
-
expect(() => Frame.fromBytes(bytes)).toThrow('Invalid error frame body:')
|
|
134
|
-
})
|
|
135
|
-
})
|
package/tests/ipld.test.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import * as http from 'node:http'
|
|
3
|
-
import { AddressInfo } from 'node:net'
|
|
4
|
-
import { CID } from 'multiformats/cid'
|
|
5
|
-
import { LexiconDoc } from '@atproto/lexicon'
|
|
6
|
-
import { XrpcClient } from '@atproto/xrpc'
|
|
7
|
-
import * as xrpcServer from '../src/index.js'
|
|
8
|
-
import { closeServer, createServer } from './_util.js'
|
|
9
|
-
|
|
10
|
-
const LEXICONS: LexiconDoc[] = [
|
|
11
|
-
{
|
|
12
|
-
lexicon: 1,
|
|
13
|
-
id: 'io.example.ipld',
|
|
14
|
-
defs: {
|
|
15
|
-
main: {
|
|
16
|
-
type: 'procedure',
|
|
17
|
-
input: {
|
|
18
|
-
encoding: 'application/json',
|
|
19
|
-
schema: {
|
|
20
|
-
type: 'object',
|
|
21
|
-
properties: {
|
|
22
|
-
cid: {
|
|
23
|
-
type: 'cid-link',
|
|
24
|
-
},
|
|
25
|
-
bytes: {
|
|
26
|
-
type: 'bytes',
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
output: {
|
|
32
|
-
encoding: 'application/json',
|
|
33
|
-
schema: {
|
|
34
|
-
type: 'object',
|
|
35
|
-
properties: {
|
|
36
|
-
cid: {
|
|
37
|
-
type: 'cid-link',
|
|
38
|
-
},
|
|
39
|
-
bytes: {
|
|
40
|
-
type: 'bytes',
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
describe('Ipld vals', () => {
|
|
51
|
-
let s: http.Server
|
|
52
|
-
const server = xrpcServer.createServer(LEXICONS)
|
|
53
|
-
server.method('io.example.ipld', (ctx) => {
|
|
54
|
-
assert(ctx.input?.body, 'expected input body')
|
|
55
|
-
assert(typeof ctx.input.body === 'object', 'expected input body')
|
|
56
|
-
|
|
57
|
-
const asCid = CID.asCID(ctx.input.body['cid'])
|
|
58
|
-
if (!(asCid instanceof CID)) {
|
|
59
|
-
throw new Error('expected cid')
|
|
60
|
-
}
|
|
61
|
-
const bytes = ctx.input.body['bytes']
|
|
62
|
-
if (!(bytes instanceof Uint8Array)) {
|
|
63
|
-
throw new Error('expected bytes')
|
|
64
|
-
}
|
|
65
|
-
return { encoding: 'application/json', body: ctx.input.body }
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
let client: XrpcClient
|
|
69
|
-
beforeAll(async () => {
|
|
70
|
-
s = await createServer(server)
|
|
71
|
-
const { port } = s.address() as AddressInfo
|
|
72
|
-
client = new XrpcClient(`http://localhost:${port}`, LEXICONS)
|
|
73
|
-
})
|
|
74
|
-
afterAll(async () => {
|
|
75
|
-
await closeServer(s)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('can send and receive ipld vals', async () => {
|
|
79
|
-
const cid = CID.parse(
|
|
80
|
-
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
81
|
-
)
|
|
82
|
-
const bytes = new Uint8Array([0, 1, 2, 3])
|
|
83
|
-
const res = await client.call(
|
|
84
|
-
'io.example.ipld',
|
|
85
|
-
{},
|
|
86
|
-
{
|
|
87
|
-
cid,
|
|
88
|
-
bytes,
|
|
89
|
-
},
|
|
90
|
-
{ encoding: 'application/json' },
|
|
91
|
-
)
|
|
92
|
-
expect(res.success).toBeTruthy()
|
|
93
|
-
expect(res.headers['content-type']).toBe('application/json; charset=utf-8')
|
|
94
|
-
expect(cid.equals(res.data.cid)).toBeTruthy()
|
|
95
|
-
expect(bytes).toEqual(res.data.bytes)
|
|
96
|
-
})
|
|
97
|
-
})
|