starpc 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/LICENSE +19 -0
- package/README.md +122 -27
- package/dist/echo/echo.d.ts +59 -0
- package/dist/echo/echo.js +85 -0
- package/dist/srpc/broadcast-channel.d.ts +16 -0
- package/dist/srpc/broadcast-channel.js +60 -0
- package/dist/srpc/client-rpc.d.ts +31 -0
- package/dist/srpc/client-rpc.js +176 -0
- package/dist/srpc/client.d.ts +12 -0
- package/dist/srpc/client.js +129 -0
- package/dist/srpc/conn.d.ts +14 -0
- package/dist/srpc/conn.js +38 -0
- package/dist/srpc/index.d.ts +3 -0
- package/dist/srpc/index.js +2 -0
- package/dist/srpc/packet.d.ts +9 -0
- package/dist/srpc/packet.js +89 -0
- package/dist/srpc/rpcproto.d.ts +194 -0
- package/dist/srpc/rpcproto.js +322 -0
- package/dist/srpc/stream.d.ts +5 -0
- package/dist/srpc/stream.js +1 -0
- package/dist/srpc/ts-proto-rpc.d.ts +7 -0
- package/dist/srpc/ts-proto-rpc.js +1 -0
- package/dist/srpc/websocket.d.ts +7 -0
- package/dist/srpc/websocket.js +18 -0
- package/echo/echo.go +1 -0
- package/echo/echo.pb.go +165 -0
- package/echo/echo.proto +19 -0
- package/echo/echo.ts +191 -0
- package/echo/echo_srpc.pb.go +333 -0
- package/echo/echo_vtproto.pb.go +271 -0
- package/echo/server.go +73 -0
- package/package.json +71 -9
- package/srpc/broadcast-channel.ts +72 -0
- package/srpc/client-rpc.go +163 -0
- package/srpc/client-rpc.ts +197 -0
- package/srpc/client.go +96 -0
- package/srpc/client.ts +182 -0
- package/srpc/conn.go +7 -0
- package/srpc/conn.ts +49 -0
- package/srpc/errors.go +20 -0
- package/srpc/handler.go +13 -0
- package/srpc/index.ts +3 -0
- package/srpc/message.go +7 -0
- package/srpc/mux.go +76 -0
- package/srpc/packet-rw.go +102 -0
- package/srpc/packet.go +71 -0
- package/srpc/packet.ts +105 -0
- package/srpc/rpc-stream.go +76 -0
- package/srpc/rpcproto.pb.go +455 -0
- package/srpc/rpcproto.proto +46 -0
- package/srpc/rpcproto.ts +467 -0
- package/srpc/rpcproto_vtproto.pb.go +1094 -0
- package/srpc/server-http.go +66 -0
- package/srpc/server-pipe.go +26 -0
- package/srpc/server-rpc.go +160 -0
- package/srpc/server.go +29 -0
- package/srpc/stream-pipe.go +86 -0
- package/srpc/stream.go +24 -0
- package/srpc/stream.ts +11 -0
- package/srpc/ts-proto-rpc.ts +29 -0
- package/srpc/websocket.go +68 -0
- package/srpc/websocket.ts +22 -0
- package/srpc/writer.go +9 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
|
|
2
|
+
// protoc-gen-go-vtproto version: v0.3.1-0.20220531071333-dfd3d322ffb6
|
|
3
|
+
// source: github.com/aperturerobotics/starpc/echo/echo.proto
|
|
4
|
+
|
|
5
|
+
package echo
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
fmt "fmt"
|
|
9
|
+
io "io"
|
|
10
|
+
bits "math/bits"
|
|
11
|
+
|
|
12
|
+
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const (
|
|
16
|
+
// Verify that this generated code is sufficiently up-to-date.
|
|
17
|
+
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
18
|
+
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
19
|
+
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
func (this *EchoMsg) EqualVT(that *EchoMsg) bool {
|
|
23
|
+
if this == nil {
|
|
24
|
+
return that == nil || that.String() == ""
|
|
25
|
+
} else if that == nil {
|
|
26
|
+
return this.String() == ""
|
|
27
|
+
}
|
|
28
|
+
if this.Body != that.Body {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
return string(this.unknownFields) == string(that.unknownFields)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func (m *EchoMsg) MarshalVT() (dAtA []byte, err error) {
|
|
35
|
+
if m == nil {
|
|
36
|
+
return nil, nil
|
|
37
|
+
}
|
|
38
|
+
size := m.SizeVT()
|
|
39
|
+
dAtA = make([]byte, size)
|
|
40
|
+
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
|
|
41
|
+
if err != nil {
|
|
42
|
+
return nil, err
|
|
43
|
+
}
|
|
44
|
+
return dAtA[:n], nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func (m *EchoMsg) MarshalToVT(dAtA []byte) (int, error) {
|
|
48
|
+
size := m.SizeVT()
|
|
49
|
+
return m.MarshalToSizedBufferVT(dAtA[:size])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func (m *EchoMsg) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
|
53
|
+
if m == nil {
|
|
54
|
+
return 0, nil
|
|
55
|
+
}
|
|
56
|
+
i := len(dAtA)
|
|
57
|
+
_ = i
|
|
58
|
+
var l int
|
|
59
|
+
_ = l
|
|
60
|
+
if m.unknownFields != nil {
|
|
61
|
+
i -= len(m.unknownFields)
|
|
62
|
+
copy(dAtA[i:], m.unknownFields)
|
|
63
|
+
}
|
|
64
|
+
if len(m.Body) > 0 {
|
|
65
|
+
i -= len(m.Body)
|
|
66
|
+
copy(dAtA[i:], m.Body)
|
|
67
|
+
i = encodeVarint(dAtA, i, uint64(len(m.Body)))
|
|
68
|
+
i--
|
|
69
|
+
dAtA[i] = 0xa
|
|
70
|
+
}
|
|
71
|
+
return len(dAtA) - i, nil
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func encodeVarint(dAtA []byte, offset int, v uint64) int {
|
|
75
|
+
offset -= sov(v)
|
|
76
|
+
base := offset
|
|
77
|
+
for v >= 1<<7 {
|
|
78
|
+
dAtA[offset] = uint8(v&0x7f | 0x80)
|
|
79
|
+
v >>= 7
|
|
80
|
+
offset++
|
|
81
|
+
}
|
|
82
|
+
dAtA[offset] = uint8(v)
|
|
83
|
+
return base
|
|
84
|
+
}
|
|
85
|
+
func (m *EchoMsg) SizeVT() (n int) {
|
|
86
|
+
if m == nil {
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
var l int
|
|
90
|
+
_ = l
|
|
91
|
+
l = len(m.Body)
|
|
92
|
+
if l > 0 {
|
|
93
|
+
n += 1 + l + sov(uint64(l))
|
|
94
|
+
}
|
|
95
|
+
n += len(m.unknownFields)
|
|
96
|
+
return n
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func sov(x uint64) (n int) {
|
|
100
|
+
return (bits.Len64(x|1) + 6) / 7
|
|
101
|
+
}
|
|
102
|
+
func soz(x uint64) (n int) {
|
|
103
|
+
return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
|
104
|
+
}
|
|
105
|
+
func (m *EchoMsg) UnmarshalVT(dAtA []byte) error {
|
|
106
|
+
l := len(dAtA)
|
|
107
|
+
iNdEx := 0
|
|
108
|
+
for iNdEx < l {
|
|
109
|
+
preIndex := iNdEx
|
|
110
|
+
var wire uint64
|
|
111
|
+
for shift := uint(0); ; shift += 7 {
|
|
112
|
+
if shift >= 64 {
|
|
113
|
+
return ErrIntOverflow
|
|
114
|
+
}
|
|
115
|
+
if iNdEx >= l {
|
|
116
|
+
return io.ErrUnexpectedEOF
|
|
117
|
+
}
|
|
118
|
+
b := dAtA[iNdEx]
|
|
119
|
+
iNdEx++
|
|
120
|
+
wire |= uint64(b&0x7F) << shift
|
|
121
|
+
if b < 0x80 {
|
|
122
|
+
break
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
fieldNum := int32(wire >> 3)
|
|
126
|
+
wireType := int(wire & 0x7)
|
|
127
|
+
if wireType == 4 {
|
|
128
|
+
return fmt.Errorf("proto: EchoMsg: wiretype end group for non-group")
|
|
129
|
+
}
|
|
130
|
+
if fieldNum <= 0 {
|
|
131
|
+
return fmt.Errorf("proto: EchoMsg: illegal tag %d (wire type %d)", fieldNum, wire)
|
|
132
|
+
}
|
|
133
|
+
switch fieldNum {
|
|
134
|
+
case 1:
|
|
135
|
+
if wireType != 2 {
|
|
136
|
+
return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
|
|
137
|
+
}
|
|
138
|
+
var stringLen uint64
|
|
139
|
+
for shift := uint(0); ; shift += 7 {
|
|
140
|
+
if shift >= 64 {
|
|
141
|
+
return ErrIntOverflow
|
|
142
|
+
}
|
|
143
|
+
if iNdEx >= l {
|
|
144
|
+
return io.ErrUnexpectedEOF
|
|
145
|
+
}
|
|
146
|
+
b := dAtA[iNdEx]
|
|
147
|
+
iNdEx++
|
|
148
|
+
stringLen |= uint64(b&0x7F) << shift
|
|
149
|
+
if b < 0x80 {
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
intStringLen := int(stringLen)
|
|
154
|
+
if intStringLen < 0 {
|
|
155
|
+
return ErrInvalidLength
|
|
156
|
+
}
|
|
157
|
+
postIndex := iNdEx + intStringLen
|
|
158
|
+
if postIndex < 0 {
|
|
159
|
+
return ErrInvalidLength
|
|
160
|
+
}
|
|
161
|
+
if postIndex > l {
|
|
162
|
+
return io.ErrUnexpectedEOF
|
|
163
|
+
}
|
|
164
|
+
m.Body = string(dAtA[iNdEx:postIndex])
|
|
165
|
+
iNdEx = postIndex
|
|
166
|
+
default:
|
|
167
|
+
iNdEx = preIndex
|
|
168
|
+
skippy, err := skip(dAtA[iNdEx:])
|
|
169
|
+
if err != nil {
|
|
170
|
+
return err
|
|
171
|
+
}
|
|
172
|
+
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
|
173
|
+
return ErrInvalidLength
|
|
174
|
+
}
|
|
175
|
+
if (iNdEx + skippy) > l {
|
|
176
|
+
return io.ErrUnexpectedEOF
|
|
177
|
+
}
|
|
178
|
+
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
|
|
179
|
+
iNdEx += skippy
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if iNdEx > l {
|
|
184
|
+
return io.ErrUnexpectedEOF
|
|
185
|
+
}
|
|
186
|
+
return nil
|
|
187
|
+
}
|
|
188
|
+
func skip(dAtA []byte) (n int, err error) {
|
|
189
|
+
l := len(dAtA)
|
|
190
|
+
iNdEx := 0
|
|
191
|
+
depth := 0
|
|
192
|
+
for iNdEx < l {
|
|
193
|
+
var wire uint64
|
|
194
|
+
for shift := uint(0); ; shift += 7 {
|
|
195
|
+
if shift >= 64 {
|
|
196
|
+
return 0, ErrIntOverflow
|
|
197
|
+
}
|
|
198
|
+
if iNdEx >= l {
|
|
199
|
+
return 0, io.ErrUnexpectedEOF
|
|
200
|
+
}
|
|
201
|
+
b := dAtA[iNdEx]
|
|
202
|
+
iNdEx++
|
|
203
|
+
wire |= (uint64(b) & 0x7F) << shift
|
|
204
|
+
if b < 0x80 {
|
|
205
|
+
break
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
wireType := int(wire & 0x7)
|
|
209
|
+
switch wireType {
|
|
210
|
+
case 0:
|
|
211
|
+
for shift := uint(0); ; shift += 7 {
|
|
212
|
+
if shift >= 64 {
|
|
213
|
+
return 0, ErrIntOverflow
|
|
214
|
+
}
|
|
215
|
+
if iNdEx >= l {
|
|
216
|
+
return 0, io.ErrUnexpectedEOF
|
|
217
|
+
}
|
|
218
|
+
iNdEx++
|
|
219
|
+
if dAtA[iNdEx-1] < 0x80 {
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
case 1:
|
|
224
|
+
iNdEx += 8
|
|
225
|
+
case 2:
|
|
226
|
+
var length int
|
|
227
|
+
for shift := uint(0); ; shift += 7 {
|
|
228
|
+
if shift >= 64 {
|
|
229
|
+
return 0, ErrIntOverflow
|
|
230
|
+
}
|
|
231
|
+
if iNdEx >= l {
|
|
232
|
+
return 0, io.ErrUnexpectedEOF
|
|
233
|
+
}
|
|
234
|
+
b := dAtA[iNdEx]
|
|
235
|
+
iNdEx++
|
|
236
|
+
length |= (int(b) & 0x7F) << shift
|
|
237
|
+
if b < 0x80 {
|
|
238
|
+
break
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if length < 0 {
|
|
242
|
+
return 0, ErrInvalidLength
|
|
243
|
+
}
|
|
244
|
+
iNdEx += length
|
|
245
|
+
case 3:
|
|
246
|
+
depth++
|
|
247
|
+
case 4:
|
|
248
|
+
if depth == 0 {
|
|
249
|
+
return 0, ErrUnexpectedEndOfGroup
|
|
250
|
+
}
|
|
251
|
+
depth--
|
|
252
|
+
case 5:
|
|
253
|
+
iNdEx += 4
|
|
254
|
+
default:
|
|
255
|
+
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
|
256
|
+
}
|
|
257
|
+
if iNdEx < 0 {
|
|
258
|
+
return 0, ErrInvalidLength
|
|
259
|
+
}
|
|
260
|
+
if depth == 0 {
|
|
261
|
+
return iNdEx, nil
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return 0, io.ErrUnexpectedEOF
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
var (
|
|
268
|
+
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
|
|
269
|
+
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
|
|
270
|
+
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
|
|
271
|
+
)
|
package/echo/server.go
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package echo
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
context "context"
|
|
5
|
+
"errors"
|
|
6
|
+
"io"
|
|
7
|
+
"time"
|
|
8
|
+
|
|
9
|
+
"google.golang.org/protobuf/proto"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// EchoServer implements the server side of Echo.
|
|
13
|
+
type EchoServer struct {
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Echo implements echo.SRPCEchoerServer
|
|
17
|
+
func (*EchoServer) Echo(ctx context.Context, msg *EchoMsg) (*EchoMsg, error) {
|
|
18
|
+
return proto.Clone(msg).(*EchoMsg), nil
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// EchoServerStream implements SRPCEchoerServer
|
|
22
|
+
func (*EchoServer) EchoServerStream(msg *EchoMsg, strm SRPCEchoer_EchoServerStreamStream) error {
|
|
23
|
+
// send 5 responses, with a 200ms delay for each
|
|
24
|
+
responses := 5
|
|
25
|
+
tkr := time.NewTicker(time.Millisecond * 200)
|
|
26
|
+
defer tkr.Stop()
|
|
27
|
+
for i := 0; i < responses; i++ {
|
|
28
|
+
if err := strm.MsgSend(msg); err != nil {
|
|
29
|
+
return err
|
|
30
|
+
}
|
|
31
|
+
select {
|
|
32
|
+
case <-strm.Context().Done():
|
|
33
|
+
return context.Canceled
|
|
34
|
+
case <-tkr.C:
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return nil
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// EchoClientStream implements SRPCEchoerServer
|
|
41
|
+
func (*EchoServer) EchoClientStream(strm SRPCEchoer_EchoClientStreamStream) error {
|
|
42
|
+
msg, err := strm.Recv()
|
|
43
|
+
if err != nil {
|
|
44
|
+
return err
|
|
45
|
+
}
|
|
46
|
+
return strm.SendAndClose(msg)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// EchoBidiStream implements SRPCEchoerServer
|
|
50
|
+
func (s *EchoServer) EchoBidiStream(strm SRPCEchoer_EchoBidiStreamStream) error {
|
|
51
|
+
// server sends initial message
|
|
52
|
+
if err := strm.MsgSend(&EchoMsg{Body: "hello from server"}); err != nil {
|
|
53
|
+
return err
|
|
54
|
+
}
|
|
55
|
+
for {
|
|
56
|
+
msg, err := strm.Recv()
|
|
57
|
+
if err != nil {
|
|
58
|
+
if err == io.EOF {
|
|
59
|
+
return nil
|
|
60
|
+
}
|
|
61
|
+
return err
|
|
62
|
+
}
|
|
63
|
+
if len(msg.GetBody()) == 0 {
|
|
64
|
+
return errors.New("got message with empty body")
|
|
65
|
+
}
|
|
66
|
+
if err := strm.Send(msg); err != nil {
|
|
67
|
+
return err
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// _ is a type assertion
|
|
73
|
+
var _ SRPCEchoerServer = ((*EchoServer)(nil))
|
package/package.json
CHANGED
|
@@ -1,15 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starpc",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Streaming protobuf RPC service protocol over any two-way channel.",
|
|
5
|
-
"
|
|
6
|
-
"license": "Apache-2.0",
|
|
5
|
+
"license": "MIT",
|
|
7
6
|
"author": {
|
|
8
|
-
"name": "
|
|
9
|
-
"email": "
|
|
10
|
-
"url": "http://
|
|
7
|
+
"name": "Aperture Robotics LLC.",
|
|
8
|
+
"email": "support@aperture.us",
|
|
9
|
+
"url": "http://aperture.us"
|
|
11
10
|
},
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
"contributors": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Christian Stewart",
|
|
14
|
+
"email": "christian@aperture.us",
|
|
15
|
+
"url": "http://github.com/paralin"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"main": "dist/srpc/index.js",
|
|
19
|
+
"types": "./dist/srpc/index.d.ts",
|
|
20
|
+
"files": [
|
|
21
|
+
"srpc",
|
|
22
|
+
"echo",
|
|
23
|
+
"dist/srpc",
|
|
24
|
+
"dist/echo",
|
|
25
|
+
"!**/*.tsbuildinfo"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"url": "git@github.com:aperturerobotics/starpc.git"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc --project tsconfig.build.json --outDir ./dist/",
|
|
32
|
+
"check": "tsc",
|
|
33
|
+
"codegen": "npm run gen",
|
|
34
|
+
"ci": "npm run build && npm run lint:js && npm run lint:go",
|
|
35
|
+
"format": "prettier --write './srpc/**/(*.ts|*.tsx|*.js|*.html|*.css)'",
|
|
36
|
+
"gen": "make genproto",
|
|
37
|
+
"test": "make test && npm run check",
|
|
38
|
+
"test:integration": "make integration",
|
|
39
|
+
"lint": "npm run lint:go && npm run lint:js",
|
|
40
|
+
"lint:go": "make lint",
|
|
41
|
+
"lint:js": "eslint -c .eslintrc.js --ext .ts ./{srpc,echo}/**/*.ts",
|
|
42
|
+
"postinstall": "patch-package",
|
|
43
|
+
"precommit": "npm run format"
|
|
44
|
+
},
|
|
45
|
+
"prettier": {
|
|
46
|
+
"semi": false,
|
|
47
|
+
"singleQuote": true
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/varint": "^6.0.0",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
|
52
|
+
"@typescript-eslint/parser": "^5.27.1",
|
|
53
|
+
"esbuild": "^0.14.43",
|
|
54
|
+
"eslint": "^8.17.0",
|
|
55
|
+
"eslint-config-prettier": "^8.5.0",
|
|
56
|
+
"eslint-config-standard-with-typescript": "^21.0.1",
|
|
57
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
58
|
+
"husky": "^8.0.1",
|
|
59
|
+
"patch-package": "^6.4.7",
|
|
60
|
+
"prettier": "^2.7.0",
|
|
61
|
+
"ts-proto": "^1.115.4",
|
|
62
|
+
"typescript": "^4.7.3"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@libp2p/mplex": "^1.2.1",
|
|
66
|
+
"event-iterator": "^2.0.0",
|
|
67
|
+
"isomorphic-ws": "^4.0.1",
|
|
68
|
+
"it-length-prefixed": "^7.0.1",
|
|
69
|
+
"it-pipe": "^2.0.3",
|
|
70
|
+
"it-pushable": "^3.0.0",
|
|
71
|
+
"it-stream-types": "^1.0.4",
|
|
72
|
+
"it-ws": "^5.0.2",
|
|
73
|
+
"protobufjs": "^6.11.3",
|
|
74
|
+
"rxjs": "^7.5.5",
|
|
75
|
+
"ws": "^8.8.0"
|
|
76
|
+
}
|
|
15
77
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Duplex, Sink } from 'it-stream-types'
|
|
2
|
+
import { Conn } from './conn'
|
|
3
|
+
import { EventIterator } from 'event-iterator'
|
|
4
|
+
import { pipe } from 'it-pipe'
|
|
5
|
+
|
|
6
|
+
// BroadcastChannelIterable is a AsyncIterable wrapper for BroadcastChannel.
|
|
7
|
+
export class BroadcastChannelIterable<T> implements Duplex<T> {
|
|
8
|
+
// channel is the broadcast channel
|
|
9
|
+
public readonly channel: BroadcastChannel
|
|
10
|
+
// sink is the sink for incoming messages.
|
|
11
|
+
public sink: Sink<T>
|
|
12
|
+
// source is the source for outgoing messages.
|
|
13
|
+
public source: AsyncIterable<T>
|
|
14
|
+
|
|
15
|
+
constructor(channel: BroadcastChannel) {
|
|
16
|
+
this.channel = channel
|
|
17
|
+
this.sink = this._createSink()
|
|
18
|
+
this.source = this._createSource()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// _createSink initializes the sink field.
|
|
22
|
+
private _createSink(): Sink<T> {
|
|
23
|
+
return async (source) => {
|
|
24
|
+
for await (const msg of source) {
|
|
25
|
+
this.channel.postMessage(msg)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// _createSource initializes the source field.
|
|
31
|
+
private _createSource() {
|
|
32
|
+
return new EventIterator<T>((queue) => {
|
|
33
|
+
const messageListener = (ev: MessageEvent<T>) => {
|
|
34
|
+
if (ev.data) {
|
|
35
|
+
queue.push(ev.data)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
this.channel.addEventListener('message', messageListener)
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
this.channel.removeEventListener('message', messageListener)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// newBroadcastChannelIterable constructs a BroadcastChannelIterable with a channel name.
|
|
48
|
+
export function newBroadcastChannelIterable<T>(
|
|
49
|
+
name: string
|
|
50
|
+
): BroadcastChannelIterable<T> {
|
|
51
|
+
const channel = new BroadcastChannel(name)
|
|
52
|
+
return new BroadcastChannelIterable<T>(channel)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// BroadcastChannelConn implements a connection with a BroadcastChannel.
|
|
56
|
+
//
|
|
57
|
+
// expects Uint8Array objects over the BroadcastChannel.
|
|
58
|
+
export class BroadcastChannelConn extends Conn {
|
|
59
|
+
// channel is the broadcast channel iterable
|
|
60
|
+
private channel: BroadcastChannelIterable<Uint8Array>
|
|
61
|
+
|
|
62
|
+
constructor(channel: BroadcastChannel) {
|
|
63
|
+
super()
|
|
64
|
+
this.channel = new BroadcastChannelIterable<Uint8Array>(channel)
|
|
65
|
+
pipe(this, this.channel, this)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// getBroadcastChannel returns the BroadcastChannel.
|
|
69
|
+
public getBroadcastChannel(): BroadcastChannel {
|
|
70
|
+
return this.channel.channel
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
"github.com/pkg/errors"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// ClientRPC represents the client side of an on-going RPC call message stream.
|
|
10
|
+
// Not concurrency safe: use a mutex if calling concurrently.
|
|
11
|
+
type ClientRPC struct {
|
|
12
|
+
// ctx is the context, canceled when the rpc ends.
|
|
13
|
+
ctx context.Context
|
|
14
|
+
// ctxCancel is called when the rpc ends.
|
|
15
|
+
ctxCancel context.CancelFunc
|
|
16
|
+
// writer is the writer to write messages to
|
|
17
|
+
writer Writer
|
|
18
|
+
// service is the rpc service
|
|
19
|
+
service string
|
|
20
|
+
// method is the rpc method
|
|
21
|
+
method string
|
|
22
|
+
// dataCh contains queued data packets.
|
|
23
|
+
// closed when the client closes the channel.
|
|
24
|
+
dataCh chan []byte
|
|
25
|
+
// dataChClosed is a flag set after dataCh is closed.
|
|
26
|
+
// controlled by HandlePacket.
|
|
27
|
+
dataChClosed bool
|
|
28
|
+
// serverErr is an error set by the client.
|
|
29
|
+
// before dataCh is closed, managed by HandlePacket.
|
|
30
|
+
// immutable after dataCh is closed.
|
|
31
|
+
serverErr error
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// NewClientRPC constructs a new ClientRPC session and writes CallStart.
|
|
35
|
+
// the writer will be closed when the ClientRPC completes.
|
|
36
|
+
// service and method must be specified.
|
|
37
|
+
// must call Start after creating the RPC object.
|
|
38
|
+
func NewClientRPC(ctx context.Context, service, method string) *ClientRPC {
|
|
39
|
+
rpc := &ClientRPC{
|
|
40
|
+
service: service,
|
|
41
|
+
method: method,
|
|
42
|
+
dataCh: make(chan []byte, 5),
|
|
43
|
+
}
|
|
44
|
+
rpc.ctx, rpc.ctxCancel = context.WithCancel(ctx)
|
|
45
|
+
return rpc
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Start sets the writer and writes the MsgSend message.
|
|
49
|
+
// firstMsg can be nil, but is usually set if this is a non-streaming rpc.
|
|
50
|
+
// must only be called once!
|
|
51
|
+
func (r *ClientRPC) Start(writer Writer, firstMsg []byte) error {
|
|
52
|
+
select {
|
|
53
|
+
case <-r.ctx.Done():
|
|
54
|
+
r.Close()
|
|
55
|
+
return context.Canceled
|
|
56
|
+
default:
|
|
57
|
+
}
|
|
58
|
+
r.writer = writer
|
|
59
|
+
pkt := NewCallStartPacket(r.service, r.method, firstMsg)
|
|
60
|
+
if err := writer.WritePacket(pkt); err != nil {
|
|
61
|
+
r.Close()
|
|
62
|
+
return err
|
|
63
|
+
}
|
|
64
|
+
return nil
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ReadAll reads all returned Data packets and returns any error.
|
|
68
|
+
// intended for use with unary rpcs.
|
|
69
|
+
func (r *ClientRPC) ReadAll() ([][]byte, error) {
|
|
70
|
+
msgs := make([][]byte, 0, 1)
|
|
71
|
+
for {
|
|
72
|
+
select {
|
|
73
|
+
case <-r.ctx.Done():
|
|
74
|
+
return msgs, context.Canceled
|
|
75
|
+
case data, ok := <-r.dataCh:
|
|
76
|
+
if !ok {
|
|
77
|
+
return msgs, r.serverErr
|
|
78
|
+
}
|
|
79
|
+
msgs = append(msgs, data)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Context is canceled when the ClientRPC is no longer valid.
|
|
85
|
+
func (r *ClientRPC) Context() context.Context {
|
|
86
|
+
return r.ctx
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// HandlePacketData handles an incoming unparsed message packet.
|
|
90
|
+
// Not concurrency safe: use a mutex if calling concurrently.
|
|
91
|
+
func (r *ClientRPC) HandlePacketData(data []byte) error {
|
|
92
|
+
pkt := &Packet{}
|
|
93
|
+
if err := pkt.UnmarshalVT(data); err != nil {
|
|
94
|
+
return err
|
|
95
|
+
}
|
|
96
|
+
return r.HandlePacket(pkt)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// HandlePacket handles an incoming parsed message packet.
|
|
100
|
+
// Not concurrency safe: use a mutex if calling concurrently.
|
|
101
|
+
func (r *ClientRPC) HandlePacket(msg *Packet) error {
|
|
102
|
+
if err := msg.Validate(); err != nil {
|
|
103
|
+
return err
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
switch b := msg.GetBody().(type) {
|
|
107
|
+
case *Packet_CallStart:
|
|
108
|
+
return r.HandleCallStart(b.CallStart)
|
|
109
|
+
case *Packet_CallData:
|
|
110
|
+
return r.HandleCallData(b.CallData)
|
|
111
|
+
case *Packet_CallStartResp:
|
|
112
|
+
return r.HandleCallStartResp(b.CallStartResp)
|
|
113
|
+
default:
|
|
114
|
+
return nil
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// HandleCallStart handles the call start packet.
|
|
119
|
+
func (r *ClientRPC) HandleCallStart(pkt *CallStart) error {
|
|
120
|
+
// server-to-client calls not supported
|
|
121
|
+
return errors.Wrap(ErrUnrecognizedPacket, "call start packet unexpected")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// HandleCallData handles the call data packet.
|
|
125
|
+
func (r *ClientRPC) HandleCallData(pkt *CallData) error {
|
|
126
|
+
if r.dataChClosed {
|
|
127
|
+
return ErrCompleted
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if data := pkt.GetData(); len(data) != 0 {
|
|
131
|
+
select {
|
|
132
|
+
case <-r.ctx.Done():
|
|
133
|
+
return context.Canceled
|
|
134
|
+
case r.dataCh <- data:
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
complete := pkt.GetComplete()
|
|
139
|
+
if err := pkt.GetError(); len(err) != 0 {
|
|
140
|
+
complete = true
|
|
141
|
+
r.serverErr = errors.New(err)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if complete {
|
|
145
|
+
r.dataChClosed = true
|
|
146
|
+
close(r.dataCh)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return nil
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// HandleCallStartResp handles the CallStartResp packet.
|
|
153
|
+
func (r *ClientRPC) HandleCallStartResp(resp *CallStartResp) error {
|
|
154
|
+
// client-side calls not supported
|
|
155
|
+
return errors.Wrap(ErrUnrecognizedPacket, "call start resp packet unexpected")
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Close releases any resources held by the ClientRPC.
|
|
159
|
+
// not concurrency safe with HandlePacket.
|
|
160
|
+
func (r *ClientRPC) Close() {
|
|
161
|
+
r.ctxCancel()
|
|
162
|
+
_ = r.writer.Close()
|
|
163
|
+
}
|