@atproto/xrpc-server 0.0.1 → 0.2.0
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/dist/auth.d.ts +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40116 -29848
- package/dist/index.js.map +4 -4
- package/dist/server.d.ts +9 -3
- package/dist/src/index.d.ts +2 -0
- package/dist/src/logger.d.ts +2 -0
- package/dist/src/server.d.ts +19 -0
- package/dist/src/types.d.ts +115 -0
- package/dist/src/util.d.ts +10 -0
- package/dist/stream/frames.d.ts +25 -0
- package/dist/stream/index.d.ts +5 -0
- package/dist/stream/logger.d.ts +2 -0
- package/dist/stream/server.d.ts +11 -0
- package/dist/stream/stream.d.ts +5 -0
- package/dist/stream/subscription.d.ts +24 -0
- package/dist/stream/types.d.ts +64 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/util.d.ts +3 -2
- package/package.json +14 -2
- package/src/auth.ts +111 -0
- package/src/index.ts +2 -0
- package/src/server.ts +148 -10
- package/src/stream/frames.ts +95 -0
- package/src/stream/index.ts +5 -0
- package/src/stream/logger.ts +5 -0
- package/src/stream/server.ts +65 -0
- package/src/stream/stream.ts +26 -0
- package/src/stream/subscription.ts +175 -0
- package/src/stream/types.ts +43 -0
- package/src/types.ts +27 -2
- package/src/util.ts +38 -7
- package/tests/_util.ts +36 -1
- package/tests/auth.test.ts +15 -36
- package/tests/bodies.test.ts +50 -9
- package/tests/errors.test.ts +38 -11
- package/tests/frames.test.ts +137 -0
- package/tests/ipld.test.ts +96 -0
- package/tests/parameters.test.ts +13 -45
- package/tests/procedures.test.ts +7 -3
- package/tests/queries.test.ts +7 -3
- package/tests/stream.test.ts +169 -0
- package/tests/subscriptions.test.ts +347 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as http from 'http'
|
|
2
|
+
import { once } from 'events'
|
|
3
|
+
import { AddressInfo } from 'net'
|
|
4
|
+
import { WebSocket } from 'ws'
|
|
5
|
+
import { XRPCError } from '@atproto/xrpc'
|
|
6
|
+
import {
|
|
7
|
+
ErrorFrame,
|
|
8
|
+
Frame,
|
|
9
|
+
MessageFrame,
|
|
10
|
+
XrpcStreamServer,
|
|
11
|
+
byFrame,
|
|
12
|
+
byMessage,
|
|
13
|
+
} from '../src'
|
|
14
|
+
|
|
15
|
+
describe('Stream', () => {
|
|
16
|
+
const wait = (ms) => new Promise((res) => setTimeout(res, ms))
|
|
17
|
+
it('streams message and info frames.', async () => {
|
|
18
|
+
const httpServer = http.createServer()
|
|
19
|
+
const server = new XrpcStreamServer({
|
|
20
|
+
server: httpServer,
|
|
21
|
+
handler: async function* () {
|
|
22
|
+
await wait(1)
|
|
23
|
+
yield new MessageFrame(1)
|
|
24
|
+
await wait(1)
|
|
25
|
+
yield new MessageFrame(2)
|
|
26
|
+
await wait(1)
|
|
27
|
+
yield new MessageFrame(3)
|
|
28
|
+
return
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
await once(httpServer.listen(), 'listening')
|
|
33
|
+
const { port } = server.wss.address() as AddressInfo
|
|
34
|
+
|
|
35
|
+
const ws = new WebSocket(`ws://localhost:${port}`)
|
|
36
|
+
const frames: Frame[] = []
|
|
37
|
+
for await (const frame of byFrame(ws)) {
|
|
38
|
+
frames.push(frame)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
expect(frames).toEqual([
|
|
42
|
+
new MessageFrame(1),
|
|
43
|
+
new MessageFrame(2),
|
|
44
|
+
new MessageFrame(3),
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
httpServer.close()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('kills handler and closes on error frame.', async () => {
|
|
51
|
+
let proceededAfterError = false
|
|
52
|
+
const httpServer = http.createServer()
|
|
53
|
+
const server = new XrpcStreamServer({
|
|
54
|
+
server: httpServer,
|
|
55
|
+
handler: async function* () {
|
|
56
|
+
await wait(1)
|
|
57
|
+
yield new MessageFrame(1)
|
|
58
|
+
await wait(1)
|
|
59
|
+
yield new MessageFrame(2)
|
|
60
|
+
await wait(1)
|
|
61
|
+
yield new ErrorFrame({ error: 'BadOops' })
|
|
62
|
+
proceededAfterError = true
|
|
63
|
+
await wait(1)
|
|
64
|
+
yield new MessageFrame(3)
|
|
65
|
+
return
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
await once(httpServer.listen(), 'listening')
|
|
70
|
+
const { port } = server.wss.address() as AddressInfo
|
|
71
|
+
|
|
72
|
+
const ws = new WebSocket(`ws://localhost:${port}`)
|
|
73
|
+
const frames: Frame[] = []
|
|
74
|
+
for await (const frame of byFrame(ws)) {
|
|
75
|
+
frames.push(frame)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await wait(5) // Ensure handler hasn't kept running
|
|
79
|
+
expect(proceededAfterError).toEqual(false)
|
|
80
|
+
|
|
81
|
+
expect(frames).toEqual([
|
|
82
|
+
new MessageFrame(1),
|
|
83
|
+
new MessageFrame(2),
|
|
84
|
+
new ErrorFrame({ error: 'BadOops' }),
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
httpServer.close()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('kills handler and closes client disconnect.', async () => {
|
|
91
|
+
const httpServer = http.createServer()
|
|
92
|
+
let i = 1
|
|
93
|
+
const server = new XrpcStreamServer({
|
|
94
|
+
server: httpServer,
|
|
95
|
+
handler: async function* () {
|
|
96
|
+
while (true) {
|
|
97
|
+
await wait(0)
|
|
98
|
+
yield new MessageFrame(i++)
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
await once(httpServer.listen(), 'listening')
|
|
104
|
+
const { port } = server.wss.address() as AddressInfo
|
|
105
|
+
|
|
106
|
+
const ws = new WebSocket(`ws://localhost:${port}`)
|
|
107
|
+
const frames: Frame[] = []
|
|
108
|
+
for await (const frame of byFrame(ws)) {
|
|
109
|
+
frames.push(frame)
|
|
110
|
+
if (frame.body === 3) ws.terminate()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Grace period to let close take place on the server
|
|
114
|
+
await wait(5)
|
|
115
|
+
// Ensure handler hasn't kept running
|
|
116
|
+
const currentCount = i
|
|
117
|
+
await wait(5)
|
|
118
|
+
expect(i).toBe(currentCount)
|
|
119
|
+
|
|
120
|
+
httpServer.close()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe('byMessage()', () => {
|
|
124
|
+
it('kills handler and closes client disconnect on error frame.', async () => {
|
|
125
|
+
const httpServer = http.createServer()
|
|
126
|
+
const server = new XrpcStreamServer({
|
|
127
|
+
server: httpServer,
|
|
128
|
+
handler: async function* () {
|
|
129
|
+
await wait(1)
|
|
130
|
+
yield new MessageFrame(1)
|
|
131
|
+
await wait(1)
|
|
132
|
+
yield new MessageFrame(2)
|
|
133
|
+
await wait(1)
|
|
134
|
+
yield new ErrorFrame({
|
|
135
|
+
error: 'BadOops',
|
|
136
|
+
message: 'That was a bad one',
|
|
137
|
+
})
|
|
138
|
+
await wait(1)
|
|
139
|
+
yield new MessageFrame(3)
|
|
140
|
+
return
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
await once(httpServer.listen(), 'listening')
|
|
144
|
+
const { port } = server.wss.address() as AddressInfo
|
|
145
|
+
|
|
146
|
+
const ws = new WebSocket(`ws://localhost:${port}`)
|
|
147
|
+
const frames: Frame[] = []
|
|
148
|
+
|
|
149
|
+
let error
|
|
150
|
+
try {
|
|
151
|
+
for await (const frame of byMessage(ws)) {
|
|
152
|
+
frames.push(frame)
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
error = err
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
expect(ws.readyState).toEqual(ws.CLOSING)
|
|
159
|
+
expect(frames).toEqual([new MessageFrame(1), new MessageFrame(2)])
|
|
160
|
+
expect(error).toBeInstanceOf(XRPCError)
|
|
161
|
+
if (error instanceof XRPCError) {
|
|
162
|
+
expect(error.error).toEqual('BadOops')
|
|
163
|
+
expect(error.message).toEqual('That was a bad one')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
httpServer.close()
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
})
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import * as http from 'http'
|
|
2
|
+
import { WebSocket, createWebSocketStream } from 'ws'
|
|
3
|
+
import getPort from 'get-port'
|
|
4
|
+
import { wait } from '@atproto/common'
|
|
5
|
+
import { byFrame, MessageFrame, ErrorFrame, Frame, Subscription } from '../src'
|
|
6
|
+
import {
|
|
7
|
+
createServer,
|
|
8
|
+
closeServer,
|
|
9
|
+
createBasicAuth,
|
|
10
|
+
basicAuthHeaders,
|
|
11
|
+
} from './_util'
|
|
12
|
+
import * as xrpcServer from '../src'
|
|
13
|
+
|
|
14
|
+
const LEXICONS = [
|
|
15
|
+
{
|
|
16
|
+
lexicon: 1,
|
|
17
|
+
id: 'io.example.stream1',
|
|
18
|
+
defs: {
|
|
19
|
+
main: {
|
|
20
|
+
type: 'subscription',
|
|
21
|
+
parameters: {
|
|
22
|
+
type: 'params',
|
|
23
|
+
required: ['countdown'],
|
|
24
|
+
properties: {
|
|
25
|
+
countdown: { type: 'integer' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
message: {
|
|
29
|
+
schema: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
required: ['count'],
|
|
32
|
+
properties: { count: { type: 'integer' } },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
lexicon: 1,
|
|
40
|
+
id: 'io.example.stream2',
|
|
41
|
+
defs: {
|
|
42
|
+
main: {
|
|
43
|
+
type: 'subscription',
|
|
44
|
+
parameters: {
|
|
45
|
+
type: 'params',
|
|
46
|
+
required: ['countdown'],
|
|
47
|
+
properties: {
|
|
48
|
+
countdown: { type: 'integer' },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
message: {
|
|
52
|
+
schema: {
|
|
53
|
+
type: 'union',
|
|
54
|
+
refs: ['#even', '#odd'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
even: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
required: ['count'],
|
|
61
|
+
properties: { count: { type: 'integer' } },
|
|
62
|
+
},
|
|
63
|
+
odd: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
required: ['count'],
|
|
66
|
+
properties: { count: { type: 'integer' } },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
lexicon: 1,
|
|
72
|
+
id: 'io.example.streamAuth',
|
|
73
|
+
defs: {
|
|
74
|
+
main: {
|
|
75
|
+
type: 'subscription',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
describe('Subscriptions', () => {
|
|
82
|
+
let s: http.Server
|
|
83
|
+
|
|
84
|
+
const server = xrpcServer.createServer(LEXICONS)
|
|
85
|
+
const lex = server.lex
|
|
86
|
+
|
|
87
|
+
server.streamMethod('io.example.stream1', async function* ({ params }) {
|
|
88
|
+
const countdown = Number(params.countdown ?? 0)
|
|
89
|
+
for (let i = countdown; i >= 0; i--) {
|
|
90
|
+
await wait(0)
|
|
91
|
+
yield { count: i }
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
server.streamMethod('io.example.stream2', async function* ({ params }) {
|
|
96
|
+
const countdown = Number(params.countdown ?? 0)
|
|
97
|
+
for (let i = countdown; i >= 0; i--) {
|
|
98
|
+
yield {
|
|
99
|
+
$type: i % 2 === 0 ? '#even' : 'io.example.stream2#odd',
|
|
100
|
+
count: i,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
yield {
|
|
104
|
+
$type: 'io.example.otherNsid#done',
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
server.streamMethod('io.example.streamAuth', {
|
|
109
|
+
auth: createBasicAuth({ username: 'admin', password: 'password' }),
|
|
110
|
+
handler: async function* ({ auth }) {
|
|
111
|
+
yield auth
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
let port: number
|
|
116
|
+
|
|
117
|
+
beforeAll(async () => {
|
|
118
|
+
port = await getPort()
|
|
119
|
+
s = await createServer(port, server)
|
|
120
|
+
})
|
|
121
|
+
afterAll(async () => {
|
|
122
|
+
await closeServer(s)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('streams messages', async () => {
|
|
126
|
+
const ws = new WebSocket(
|
|
127
|
+
`ws://localhost:${port}/xrpc/io.example.stream1?countdown=5`,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const frames: Frame[] = []
|
|
131
|
+
for await (const frame of byFrame(ws)) {
|
|
132
|
+
frames.push(frame)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
expect(frames).toEqual([
|
|
136
|
+
new MessageFrame({ count: 5 }),
|
|
137
|
+
new MessageFrame({ count: 4 }),
|
|
138
|
+
new MessageFrame({ count: 3 }),
|
|
139
|
+
new MessageFrame({ count: 2 }),
|
|
140
|
+
new MessageFrame({ count: 1 }),
|
|
141
|
+
new MessageFrame({ count: 0 }),
|
|
142
|
+
])
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('streams messages in a union', async () => {
|
|
146
|
+
const ws = new WebSocket(
|
|
147
|
+
`ws://localhost:${port}/xrpc/io.example.stream2?countdown=5`,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const frames: Frame[] = []
|
|
151
|
+
for await (const frame of byFrame(ws)) {
|
|
152
|
+
frames.push(frame)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
expect(frames).toEqual([
|
|
156
|
+
new MessageFrame({ count: 5 }, { type: '#odd' }),
|
|
157
|
+
new MessageFrame({ count: 4 }, { type: '#even' }),
|
|
158
|
+
new MessageFrame({ count: 3 }, { type: '#odd' }),
|
|
159
|
+
new MessageFrame({ count: 2 }, { type: '#even' }),
|
|
160
|
+
new MessageFrame({ count: 1 }, { type: '#odd' }),
|
|
161
|
+
new MessageFrame({ count: 0 }, { type: '#even' }),
|
|
162
|
+
new MessageFrame({}, { type: 'io.example.otherNsid#done' }),
|
|
163
|
+
])
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('resolves auth into handler', async () => {
|
|
167
|
+
const ws = new WebSocket(
|
|
168
|
+
`ws://localhost:${port}/xrpc/io.example.streamAuth`,
|
|
169
|
+
{
|
|
170
|
+
headers: basicAuthHeaders({
|
|
171
|
+
username: 'admin',
|
|
172
|
+
password: 'password',
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const frames: Frame[] = []
|
|
178
|
+
for await (const frame of byFrame(ws)) {
|
|
179
|
+
frames.push(frame)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
expect(frames).toEqual([
|
|
183
|
+
new MessageFrame({
|
|
184
|
+
credentials: {
|
|
185
|
+
username: 'admin',
|
|
186
|
+
},
|
|
187
|
+
artifacts: {
|
|
188
|
+
original: 'YWRtaW46cGFzc3dvcmQ=',
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
])
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('errors immediately on bad parameter', async () => {
|
|
195
|
+
const ws = new WebSocket(`ws://localhost:${port}/xrpc/io.example.stream1`)
|
|
196
|
+
|
|
197
|
+
const frames: Frame[] = []
|
|
198
|
+
for await (const frame of byFrame(ws)) {
|
|
199
|
+
frames.push(frame)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
expect(frames).toEqual([
|
|
203
|
+
new ErrorFrame({
|
|
204
|
+
error: 'InvalidRequest',
|
|
205
|
+
message: 'Error: Params must have the property "countdown"',
|
|
206
|
+
}),
|
|
207
|
+
])
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('errors immediately on bad auth', async () => {
|
|
211
|
+
const ws = new WebSocket(
|
|
212
|
+
`ws://localhost:${port}/xrpc/io.example.streamAuth`,
|
|
213
|
+
{
|
|
214
|
+
headers: basicAuthHeaders({
|
|
215
|
+
username: 'bad',
|
|
216
|
+
password: 'wrong',
|
|
217
|
+
}),
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
const frames: Frame[] = []
|
|
222
|
+
for await (const frame of byFrame(ws)) {
|
|
223
|
+
frames.push(frame)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
expect(frames).toEqual([
|
|
227
|
+
new ErrorFrame({
|
|
228
|
+
error: 'AuthenticationRequired',
|
|
229
|
+
message: 'Authentication Required',
|
|
230
|
+
}),
|
|
231
|
+
])
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('does not websocket upgrade at bad endpoint', async () => {
|
|
235
|
+
const ws = new WebSocket(`ws://localhost:${port}/xrpc/does.not.exist`)
|
|
236
|
+
const drainStream = async () => {
|
|
237
|
+
for await (const bytes of createWebSocketStream(ws)) {
|
|
238
|
+
bytes // drain
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
await expect(drainStream).rejects.toHaveProperty('code', 'ECONNRESET')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
describe('Subscription consumer', () => {
|
|
245
|
+
it('receives messages w/ skips', async () => {
|
|
246
|
+
const sub = new Subscription({
|
|
247
|
+
service: `ws://localhost:${port}`,
|
|
248
|
+
method: 'io.example.stream1',
|
|
249
|
+
getParams: () => ({ countdown: 5 }),
|
|
250
|
+
validate: (obj) => {
|
|
251
|
+
const result = lex.assertValidXrpcMessage<{ count: number }>(
|
|
252
|
+
'io.example.stream1',
|
|
253
|
+
obj,
|
|
254
|
+
)
|
|
255
|
+
if (!result.count || result.count % 2) {
|
|
256
|
+
return result
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const messages: { count: number }[] = []
|
|
262
|
+
for await (const msg of sub) {
|
|
263
|
+
messages.push(msg)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
expect(messages).toEqual([
|
|
267
|
+
{ count: 5 },
|
|
268
|
+
{ count: 3 },
|
|
269
|
+
{ count: 1 },
|
|
270
|
+
{ count: 0 },
|
|
271
|
+
])
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('reconnects w/ param update', async () => {
|
|
275
|
+
let countdown = 10
|
|
276
|
+
let reconnects = 0
|
|
277
|
+
const sub = new Subscription({
|
|
278
|
+
service: `ws://localhost:${port}`,
|
|
279
|
+
method: 'io.example.stream1',
|
|
280
|
+
onReconnectError: () => reconnects++,
|
|
281
|
+
getParams: () => ({ countdown }),
|
|
282
|
+
validate: (obj) => {
|
|
283
|
+
return lex.assertValidXrpcMessage<{ count: number }>(
|
|
284
|
+
'io.example.stream1',
|
|
285
|
+
obj,
|
|
286
|
+
)
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
let disconnected = false
|
|
291
|
+
for await (const msg of sub) {
|
|
292
|
+
expect(msg.count).toBeGreaterThanOrEqual(countdown - 1) // No skips
|
|
293
|
+
countdown = Math.min(countdown, msg.count) // Only allow forward movement
|
|
294
|
+
if (msg.count <= 6 && !disconnected) {
|
|
295
|
+
disconnected = true
|
|
296
|
+
server.subscriptions.forEach(({ wss }) => {
|
|
297
|
+
wss.clients.forEach((c) => c.terminate())
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
expect(countdown).toEqual(0)
|
|
303
|
+
expect(reconnects).toBeGreaterThan(0)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('aborts with signal', async () => {
|
|
307
|
+
const abortController = new AbortController()
|
|
308
|
+
const sub = new Subscription({
|
|
309
|
+
service: `ws://localhost:${port}`,
|
|
310
|
+
method: 'io.example.stream1',
|
|
311
|
+
signal: abortController.signal,
|
|
312
|
+
getParams: () => ({ countdown: 10 }),
|
|
313
|
+
validate: (obj) => {
|
|
314
|
+
const result = lex.assertValidXrpcMessage<{ count: number }>(
|
|
315
|
+
'io.example.stream1',
|
|
316
|
+
obj,
|
|
317
|
+
)
|
|
318
|
+
return result
|
|
319
|
+
},
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
let error
|
|
323
|
+
let disconnected = false
|
|
324
|
+
const messages: { count: number }[] = []
|
|
325
|
+
try {
|
|
326
|
+
for await (const msg of sub) {
|
|
327
|
+
messages.push(msg)
|
|
328
|
+
if (msg.count <= 6 && !disconnected) {
|
|
329
|
+
disconnected = true
|
|
330
|
+
abortController.abort(new Error('Oops!'))
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch (err) {
|
|
334
|
+
error = err
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
expect(error).toEqual(new Error('Oops!'))
|
|
338
|
+
expect(messages).toEqual([
|
|
339
|
+
{ count: 10 },
|
|
340
|
+
{ count: 9 },
|
|
341
|
+
{ count: 8 },
|
|
342
|
+
{ count: 7 },
|
|
343
|
+
{ count: 6 },
|
|
344
|
+
])
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
})
|