@atproto/xrpc-server 0.0.1 → 0.1.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/index.d.ts +1 -0
- package/dist/index.js +10537 -2765
- package/dist/index.js.map +4 -4
- package/dist/server.d.ts +9 -3
- 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/types.d.ts +15 -0
- package/dist/util.d.ts +3 -2
- package/package.json +8 -2
- package/src/index.ts +1 -0
- package/src/server.ts +139 -9
- 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 +60 -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 +26 -2
- package/src/util.ts +38 -7
- package/tests/_util.ts +36 -1
- package/tests/auth.test.ts +13 -34
- package/tests/bodies.test.ts +50 -9
- package/tests/errors.test.ts +9 -3
- package/tests/frames.test.ts +137 -0
- package/tests/ipld.test.ts +96 -0
- package/tests/parameters.test.ts +13 -4
- 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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as cborx from 'cbor-x'
|
|
2
|
+
import * as uint8arrays from 'uint8arrays'
|
|
3
|
+
import { MessageFrame, ErrorFrame, Frame, FrameType } from '../src'
|
|
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
|
+
uint8arrays.equals(
|
|
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
|
+
uint8arrays.equals(
|
|
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.from('')
|
|
85
|
+
expect(() => Frame.fromBytes(bytes)).toThrow('Unexpected end of CBOR data')
|
|
86
|
+
expect(() => Frame.fromBytes(emptyBytes)).toThrow(
|
|
87
|
+
'Unexpected end of CBOR data',
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('parsing fails when frame header is malformed.', async () => {
|
|
92
|
+
const bytes = uint8arrays.concat([
|
|
93
|
+
cborx.encode({ op: -2 }), // Unknown op
|
|
94
|
+
cborx.encode({ a: 'b', c: [1, 2, 3] }),
|
|
95
|
+
])
|
|
96
|
+
|
|
97
|
+
expect(() => Frame.fromBytes(bytes)).toThrow('Invalid frame header:')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('parsing fails when frame is missing body.', async () => {
|
|
101
|
+
const messageFrame = new MessageFrame(
|
|
102
|
+
{ a: 'b', c: [1, 2, 3] },
|
|
103
|
+
{ type: '#d' },
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
const headerBytes = cborx.encode(messageFrame.header)
|
|
107
|
+
|
|
108
|
+
expect(() => Frame.fromBytes(headerBytes)).toThrow('Missing frame body')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('parsing fails when frame has too many data items.', async () => {
|
|
112
|
+
const messageFrame = new MessageFrame(
|
|
113
|
+
{ a: 'b', c: [1, 2, 3] },
|
|
114
|
+
{ type: '#d' },
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const bytes = uint8arrays.concat([
|
|
118
|
+
messageFrame.toBytes(),
|
|
119
|
+
cborx.encode({ d: 'e', f: [4, 5, 6] }),
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
expect(() => Frame.fromBytes(bytes)).toThrow(
|
|
123
|
+
'Too many CBOR data items in frame',
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('parsing fails when error frame has invalid body.', async () => {
|
|
128
|
+
const errorFrame = new ErrorFrame({ error: 'BadOops' })
|
|
129
|
+
|
|
130
|
+
const bytes = uint8arrays.concat([
|
|
131
|
+
cborx.encode(errorFrame.header),
|
|
132
|
+
cborx.encode({ blah: 1 }),
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
expect(() => Frame.fromBytes(bytes)).toThrow('Invalid error frame body:')
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as http from 'http'
|
|
2
|
+
import xrpc, { ServiceClient } from '@atproto/xrpc'
|
|
3
|
+
import { CID } from 'multiformats/cid'
|
|
4
|
+
import getPort from 'get-port'
|
|
5
|
+
import { createServer, closeServer } from './_util'
|
|
6
|
+
import * as xrpcServer from '../src'
|
|
7
|
+
|
|
8
|
+
const LEXICONS = [
|
|
9
|
+
{
|
|
10
|
+
lexicon: 1,
|
|
11
|
+
id: 'io.example.ipld',
|
|
12
|
+
defs: {
|
|
13
|
+
main: {
|
|
14
|
+
type: 'procedure',
|
|
15
|
+
input: {
|
|
16
|
+
encoding: 'application/json',
|
|
17
|
+
schema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
cid: {
|
|
21
|
+
type: 'cid-link',
|
|
22
|
+
},
|
|
23
|
+
bytes: {
|
|
24
|
+
type: 'bytes',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
output: {
|
|
30
|
+
encoding: 'application/json',
|
|
31
|
+
schema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
cid: {
|
|
35
|
+
type: 'cid-link',
|
|
36
|
+
},
|
|
37
|
+
bytes: {
|
|
38
|
+
type: 'bytes',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
describe('Ipld vals', () => {
|
|
49
|
+
let s: http.Server
|
|
50
|
+
const server = xrpcServer.createServer(LEXICONS)
|
|
51
|
+
server.method(
|
|
52
|
+
'io.example.ipld',
|
|
53
|
+
(ctx: { input?: xrpcServer.HandlerInput }) => {
|
|
54
|
+
const asCid = CID.asCID(ctx.input?.body.cid)
|
|
55
|
+
if (!(asCid instanceof CID)) {
|
|
56
|
+
throw new Error('expected cid')
|
|
57
|
+
}
|
|
58
|
+
const bytes = ctx.input?.body.bytes
|
|
59
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
60
|
+
throw new Error('expected bytes')
|
|
61
|
+
}
|
|
62
|
+
return { encoding: 'application/json', body: ctx.input?.body }
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
xrpc.addLexicons(LEXICONS)
|
|
66
|
+
|
|
67
|
+
let client: ServiceClient
|
|
68
|
+
beforeAll(async () => {
|
|
69
|
+
const port = await getPort()
|
|
70
|
+
s = await createServer(port, server)
|
|
71
|
+
client = xrpc.service(`http://localhost:${port}`)
|
|
72
|
+
})
|
|
73
|
+
afterAll(async () => {
|
|
74
|
+
await closeServer(s)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('can send and receive ipld vals', async () => {
|
|
78
|
+
const cid = CID.parse(
|
|
79
|
+
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
80
|
+
)
|
|
81
|
+
const bytes = new Uint8Array([0, 1, 2, 3])
|
|
82
|
+
const res = await client.call(
|
|
83
|
+
'io.example.ipld',
|
|
84
|
+
{},
|
|
85
|
+
{
|
|
86
|
+
cid,
|
|
87
|
+
bytes,
|
|
88
|
+
},
|
|
89
|
+
{ encoding: 'application/json' },
|
|
90
|
+
)
|
|
91
|
+
expect(res.success).toBeTruthy()
|
|
92
|
+
expect(res.headers['content-type']).toBe('application/json; charset=utf-8')
|
|
93
|
+
expect(cid.equals(res.data.cid)).toBeTruthy()
|
|
94
|
+
expect(bytes).toEqual(res.data.bytes)
|
|
95
|
+
})
|
|
96
|
+
})
|
package/tests/parameters.test.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as http from 'http'
|
|
2
|
+
import getPort from 'get-port'
|
|
3
|
+
import xrpc, { ServiceClient } from '@atproto/xrpc'
|
|
2
4
|
import { createServer, closeServer } from './_util'
|
|
3
5
|
import * as xrpcServer from '../src'
|
|
4
|
-
import xrpc from '@atproto/xrpc'
|
|
5
6
|
|
|
6
7
|
const LEXICONS = [
|
|
7
8
|
{
|
|
@@ -16,9 +17,10 @@ const LEXICONS = [
|
|
|
16
17
|
properties: {
|
|
17
18
|
str: { type: 'string', minLength: 2, maxLength: 10 },
|
|
18
19
|
int: { type: 'integer', minimum: 2, maximum: 10 },
|
|
19
|
-
num: { type: '
|
|
20
|
+
num: { type: 'float', minimum: 2, maximum: 10 },
|
|
20
21
|
bool: { type: 'boolean' },
|
|
21
22
|
arr: { type: 'array', items: { type: 'integer' }, maxLength: 2 },
|
|
23
|
+
def: { type: 'integer', default: 0 },
|
|
22
24
|
},
|
|
23
25
|
},
|
|
24
26
|
output: {
|
|
@@ -39,10 +41,13 @@ describe('Parameters', () => {
|
|
|
39
41
|
body: ctx.params,
|
|
40
42
|
}),
|
|
41
43
|
)
|
|
42
|
-
const client = xrpc.service(`http://localhost:8889`)
|
|
43
44
|
xrpc.addLexicons(LEXICONS)
|
|
45
|
+
|
|
46
|
+
let client: ServiceClient
|
|
44
47
|
beforeAll(async () => {
|
|
45
|
-
|
|
48
|
+
const port = await getPort()
|
|
49
|
+
s = await createServer(port, server)
|
|
50
|
+
client = xrpc.service(`http://localhost:${port}`)
|
|
46
51
|
})
|
|
47
52
|
afterAll(async () => {
|
|
48
53
|
await closeServer(s)
|
|
@@ -55,6 +60,7 @@ describe('Parameters', () => {
|
|
|
55
60
|
num: 5.5,
|
|
56
61
|
bool: true,
|
|
57
62
|
arr: [1, 2],
|
|
63
|
+
def: 5,
|
|
58
64
|
})
|
|
59
65
|
expect(res1.success).toBeTruthy()
|
|
60
66
|
expect(res1.data.str).toBe('valid')
|
|
@@ -62,6 +68,7 @@ describe('Parameters', () => {
|
|
|
62
68
|
expect(res1.data.num).toBe(5.5)
|
|
63
69
|
expect(res1.data.bool).toBe(true)
|
|
64
70
|
expect(res1.data.arr).toEqual([1, 2])
|
|
71
|
+
expect(res1.data.def).toEqual(5)
|
|
65
72
|
|
|
66
73
|
const res2 = await client.call('io.example.paramTest', {
|
|
67
74
|
str: 10,
|
|
@@ -76,7 +83,9 @@ describe('Parameters', () => {
|
|
|
76
83
|
expect(res2.data.num).toBe(5.5)
|
|
77
84
|
expect(res2.data.bool).toBe(true)
|
|
78
85
|
expect(res2.data.arr).toEqual([3])
|
|
86
|
+
expect(res2.data.def).toEqual(0)
|
|
79
87
|
|
|
88
|
+
// @TODO test sending blatantly bad types
|
|
80
89
|
await expect(
|
|
81
90
|
client.call('io.example.paramTest', {
|
|
82
91
|
str: 'n',
|
package/tests/procedures.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as http from 'http'
|
|
2
2
|
import { Readable } from 'stream'
|
|
3
|
-
import xrpc from '@atproto/xrpc'
|
|
3
|
+
import xrpc, { ServiceClient } from '@atproto/xrpc'
|
|
4
|
+
import getPort from 'get-port'
|
|
4
5
|
import { createServer, closeServer } from './_util'
|
|
5
6
|
import * as xrpcServer from '../src'
|
|
6
7
|
|
|
@@ -119,10 +120,13 @@ describe('Procedures', () => {
|
|
|
119
120
|
}
|
|
120
121
|
},
|
|
121
122
|
)
|
|
122
|
-
const client = xrpc.service(`http://localhost:8891`)
|
|
123
123
|
xrpc.addLexicons(LEXICONS)
|
|
124
|
+
|
|
125
|
+
let client: ServiceClient
|
|
124
126
|
beforeAll(async () => {
|
|
125
|
-
|
|
127
|
+
const port = await getPort()
|
|
128
|
+
s = await createServer(port, server)
|
|
129
|
+
client = xrpc.service(`http://localhost:${port}`)
|
|
126
130
|
})
|
|
127
131
|
afterAll(async () => {
|
|
128
132
|
await closeServer(s)
|
package/tests/queries.test.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as http from 'http'
|
|
2
|
+
import getPort from 'get-port'
|
|
3
|
+
import xrpc, { ServiceClient } from '@atproto/xrpc'
|
|
2
4
|
import { createServer, closeServer } from './_util'
|
|
3
5
|
import * as xrpcServer from '../src'
|
|
4
|
-
import xrpc from '@atproto/xrpc'
|
|
5
6
|
|
|
6
7
|
const LEXICONS = [
|
|
7
8
|
{
|
|
@@ -83,10 +84,13 @@ describe('Queries', () => {
|
|
|
83
84
|
body: { message: ctx.params.message },
|
|
84
85
|
}
|
|
85
86
|
})
|
|
86
|
-
const client = xrpc.service(`http://localhost:8890`)
|
|
87
87
|
xrpc.addLexicons(LEXICONS)
|
|
88
|
+
|
|
89
|
+
let client: ServiceClient
|
|
88
90
|
beforeAll(async () => {
|
|
89
|
-
|
|
91
|
+
const port = await getPort()
|
|
92
|
+
s = await createServer(port, server)
|
|
93
|
+
client = xrpc.service(`http://localhost:${port}`)
|
|
90
94
|
})
|
|
91
95
|
afterAll(async () => {
|
|
92
96
|
await closeServer(s)
|
|
@@ -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
|
+
})
|