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,197 @@
|
|
|
1
|
+
import type { CallData, CallStart, CallStartResp } from './rpcproto'
|
|
2
|
+
import { Packet } from './rpcproto'
|
|
3
|
+
import type { Sink } from 'it-stream-types'
|
|
4
|
+
import { pushable } from 'it-pushable'
|
|
5
|
+
|
|
6
|
+
// DataCb is a callback to handle incoming RPC messages.
|
|
7
|
+
// Returns true if more data is expected, false otherwise.
|
|
8
|
+
// If returns undefined, assumes more data is expected.
|
|
9
|
+
export type DataCb = (data: Uint8Array) => Promise<boolean | void>
|
|
10
|
+
|
|
11
|
+
// ClientRPC is an ongoing RPC from the client side.
|
|
12
|
+
export class ClientRPC {
|
|
13
|
+
// sink is the data sink for incoming messages.
|
|
14
|
+
public sink: Sink<Packet>
|
|
15
|
+
// source is the packet source for outgoing Packets.
|
|
16
|
+
public source: AsyncIterable<Packet>
|
|
17
|
+
// _source is used to write to the source.
|
|
18
|
+
private readonly _source: {
|
|
19
|
+
push: (val: Packet) => void
|
|
20
|
+
end: (err?: Error) => void
|
|
21
|
+
}
|
|
22
|
+
// service is the rpc service
|
|
23
|
+
private service: string
|
|
24
|
+
// method is the rpc method
|
|
25
|
+
private method: string
|
|
26
|
+
// dataCb is called with any incoming data.
|
|
27
|
+
private dataCb?: DataCb
|
|
28
|
+
// started is resolved when the request starts.
|
|
29
|
+
private started: Promise<void>
|
|
30
|
+
// onStarted is called by the message handler when the request starts.
|
|
31
|
+
private onStarted?: (err?: Error) => void
|
|
32
|
+
// complete is resolved when the request completes.
|
|
33
|
+
// rejected with an error if the call encountered any error.
|
|
34
|
+
private complete: Promise<void>
|
|
35
|
+
// onComplete is called by the message handler when the call completes.
|
|
36
|
+
private onComplete?: (err?: Error) => void
|
|
37
|
+
// closed indicates close has been called
|
|
38
|
+
private closed: boolean
|
|
39
|
+
|
|
40
|
+
constructor(service: string, method: string, dataCb: DataCb | null) {
|
|
41
|
+
this.closed = false
|
|
42
|
+
this.sink = this._createSink()
|
|
43
|
+
const sourcev = this._createSource()
|
|
44
|
+
this.source = sourcev
|
|
45
|
+
this._source = sourcev
|
|
46
|
+
this.service = service
|
|
47
|
+
this.method = method
|
|
48
|
+
if (dataCb) {
|
|
49
|
+
this.dataCb = dataCb
|
|
50
|
+
}
|
|
51
|
+
this.started = new Promise<void>((resolveStarted, rejectStarted) => {
|
|
52
|
+
this.onStarted = (err?: Error) => {
|
|
53
|
+
if (err) {
|
|
54
|
+
rejectStarted(err)
|
|
55
|
+
} else {
|
|
56
|
+
resolveStarted()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
this.complete = new Promise<void>((resolveComplete, rejectComplete) => {
|
|
61
|
+
this.onComplete = (err?: Error) => {
|
|
62
|
+
this.closed = true
|
|
63
|
+
if (err) {
|
|
64
|
+
rejectComplete(err)
|
|
65
|
+
} else {
|
|
66
|
+
resolveComplete()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// waitStarted returns the started promise.
|
|
73
|
+
public waitStarted(): Promise<void> {
|
|
74
|
+
return this.started
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// waitComplete returns the complete promise.
|
|
78
|
+
public waitComplete(): Promise<void> {
|
|
79
|
+
return this.complete
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// writeCallStart writes the call start packet.
|
|
83
|
+
public async writeCallStart(data?: Uint8Array) {
|
|
84
|
+
const callStart: CallStart = {
|
|
85
|
+
rpcService: this.service,
|
|
86
|
+
rpcMethod: this.method,
|
|
87
|
+
data: data || new Uint8Array(0),
|
|
88
|
+
}
|
|
89
|
+
await this.writePacket({
|
|
90
|
+
body: {
|
|
91
|
+
$case: 'callStart',
|
|
92
|
+
callStart,
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// writeCallData writes the call data packet.
|
|
98
|
+
public async writeCallData(data: Uint8Array, complete?: boolean, error?: string) {
|
|
99
|
+
const callData: CallData = {
|
|
100
|
+
data,
|
|
101
|
+
complete: complete || false,
|
|
102
|
+
error: error || "",
|
|
103
|
+
}
|
|
104
|
+
await this.writePacket({
|
|
105
|
+
body: {
|
|
106
|
+
$case: 'callData',
|
|
107
|
+
callData,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// writePacket writes a packet to the stream.
|
|
113
|
+
private async writePacket(packet: Packet) {
|
|
114
|
+
this._source.push(packet)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// handleMessage handles an incoming encoded Packet.
|
|
118
|
+
//
|
|
119
|
+
// note: may throw an error if the message was unexpected or invalid.
|
|
120
|
+
public async handleMessage(message: Uint8Array) {
|
|
121
|
+
return this.handlePacket(Packet.decode(message))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// handlePacket handles an incoming packet.
|
|
125
|
+
public async handlePacket(packet: Partial<Packet>) {
|
|
126
|
+
switch (packet?.body?.$case) {
|
|
127
|
+
case 'callStart':
|
|
128
|
+
return this.handleCallStart(packet.body.callStart)
|
|
129
|
+
case 'callStartResp':
|
|
130
|
+
return this.handleCallStartResp(packet.body.callStartResp)
|
|
131
|
+
case 'callData':
|
|
132
|
+
return this.handleCallData(packet.body.callData)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// handleCallStart handles a CallStart packet.
|
|
137
|
+
public async handleCallStart(packet: Partial<CallStart>) {
|
|
138
|
+
// we do not implement server -> client RPCs.
|
|
139
|
+
throw new Error(`unexpected server to client rpc: ${packet.rpcService}/${packet.rpcMethod}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// handleCallStartResp handles a CallStartResp packet.
|
|
143
|
+
public async handleCallStartResp(packet: Partial<CallStartResp>) {
|
|
144
|
+
if (packet.error && packet.error.length) {
|
|
145
|
+
const err = new Error(packet.error)
|
|
146
|
+
this.onStarted!(err)
|
|
147
|
+
this.onComplete!(err)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// handleCallData handles a CallData packet.
|
|
152
|
+
public async handleCallData(packet: Partial<CallData>) {
|
|
153
|
+
const data = packet.data
|
|
154
|
+
if (this.dataCb && data?.length) {
|
|
155
|
+
await this.dataCb(data)
|
|
156
|
+
}
|
|
157
|
+
if (packet.error && packet.error.length) {
|
|
158
|
+
this.onComplete!(new Error(packet.error))
|
|
159
|
+
} else if (packet.complete) {
|
|
160
|
+
this.onComplete!()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// close closes the active call if not already completed.
|
|
165
|
+
public async close(err?: Error) {
|
|
166
|
+
if (!this.closed) {
|
|
167
|
+
await this.writeCallData(new Uint8Array(0), true, err ? err.message : "")
|
|
168
|
+
}
|
|
169
|
+
if (!err) {
|
|
170
|
+
err = new Error('call closed')
|
|
171
|
+
}
|
|
172
|
+
this.onComplete!(err)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// _createSink initializes the sink field.
|
|
176
|
+
private _createSink(): Sink<Packet> {
|
|
177
|
+
return async (source) => {
|
|
178
|
+
try {
|
|
179
|
+
for await (const msg of source) {
|
|
180
|
+
await this.handlePacket(msg)
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
this.close(err as Error)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// _createSource initializes the source field.
|
|
189
|
+
private _createSource() {
|
|
190
|
+
return pushable<Packet>({
|
|
191
|
+
objectMode: true,
|
|
192
|
+
onEnd: (err?: Error): void => {
|
|
193
|
+
this.onComplete!(err)
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
}
|
package/srpc/client.go
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"io"
|
|
6
|
+
|
|
7
|
+
"github.com/pkg/errors"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// Client implements a SRPC client which can initiate RPC streams.
|
|
11
|
+
type Client interface {
|
|
12
|
+
// Invoke executes a unary RPC with the remote.
|
|
13
|
+
Invoke(ctx context.Context, service, method string, in, out Message) error
|
|
14
|
+
|
|
15
|
+
// NewStream starts a streaming RPC with the remote & returns the stream.
|
|
16
|
+
// firstMsg is optional.
|
|
17
|
+
NewStream(ctx context.Context, service, method string, firstMsg Message) (Stream, error)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// OpenStreamFunc opens a stream with a remote.
|
|
21
|
+
// msgHandler must not be called concurrently.
|
|
22
|
+
type OpenStreamFunc = func(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error)
|
|
23
|
+
|
|
24
|
+
// client implements Client with a transport.
|
|
25
|
+
type client struct {
|
|
26
|
+
// openStream opens a new stream.
|
|
27
|
+
openStream OpenStreamFunc
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// NewClient constructs a client with a OpenStreamFunc.
|
|
31
|
+
func NewClient(openStream OpenStreamFunc) Client {
|
|
32
|
+
return &client{
|
|
33
|
+
openStream: openStream,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Invoke executes a unary RPC with the remote.
|
|
38
|
+
func (c *client) Invoke(rctx context.Context, service, method string, in, out Message) error {
|
|
39
|
+
ctx, ctxCancel := context.WithCancel(rctx)
|
|
40
|
+
defer ctxCancel()
|
|
41
|
+
|
|
42
|
+
firstMsg, err := in.MarshalVT()
|
|
43
|
+
if err != nil {
|
|
44
|
+
return err
|
|
45
|
+
}
|
|
46
|
+
clientRPC := NewClientRPC(ctx, service, method)
|
|
47
|
+
writer, err := c.openStream(ctx, clientRPC.HandlePacket)
|
|
48
|
+
if err != nil {
|
|
49
|
+
return err
|
|
50
|
+
}
|
|
51
|
+
if err := clientRPC.Start(writer, firstMsg); err != nil {
|
|
52
|
+
return err
|
|
53
|
+
}
|
|
54
|
+
msgs, err := clientRPC.ReadAll()
|
|
55
|
+
if err != nil {
|
|
56
|
+
// this includes any server returned error.
|
|
57
|
+
return err
|
|
58
|
+
}
|
|
59
|
+
if len(msgs) == 0 {
|
|
60
|
+
// no reply? return eof.
|
|
61
|
+
return io.EOF
|
|
62
|
+
}
|
|
63
|
+
// parse first message to out
|
|
64
|
+
if err := out.UnmarshalVT(msgs[0]); err != nil {
|
|
65
|
+
return errors.Wrap(ErrInvalidMessage, err.Error())
|
|
66
|
+
}
|
|
67
|
+
// done
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// NewStream starts a streaming RPC with the remote & returns the stream.
|
|
72
|
+
// firstMsg is optional.
|
|
73
|
+
func (c *client) NewStream(ctx context.Context, service, method string, firstMsg Message) (Stream, error) {
|
|
74
|
+
var firstMsgData []byte
|
|
75
|
+
if firstMsg != nil {
|
|
76
|
+
var err error
|
|
77
|
+
firstMsgData, err = firstMsg.MarshalVT()
|
|
78
|
+
if err != nil {
|
|
79
|
+
return nil, err
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clientRPC := NewClientRPC(ctx, service, method)
|
|
84
|
+
writer, err := c.openStream(ctx, clientRPC.HandlePacket)
|
|
85
|
+
if err != nil {
|
|
86
|
+
return nil, err
|
|
87
|
+
}
|
|
88
|
+
if err := clientRPC.Start(writer, firstMsgData); err != nil {
|
|
89
|
+
return nil, err
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return NewRPCStream(ctx, clientRPC.writer, clientRPC.dataCh), nil
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// _ is a type assertion
|
|
96
|
+
var _ Client = ((*client)(nil))
|
package/srpc/client.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Observable, from as observableFrom } from 'rxjs'
|
|
2
|
+
import type { TsProtoRpc } from './ts-proto-rpc'
|
|
3
|
+
import type { OpenStreamFunc } from './stream'
|
|
4
|
+
import { DataCb, ClientRPC } from './client-rpc'
|
|
5
|
+
import { pipe } from 'it-pipe'
|
|
6
|
+
import { pushable, Pushable } from 'it-pushable'
|
|
7
|
+
import {
|
|
8
|
+
decodePacketSource,
|
|
9
|
+
encodePacketSource,
|
|
10
|
+
parseLengthPrefixTransform,
|
|
11
|
+
prependLengthPrefixTransform,
|
|
12
|
+
} from './packet'
|
|
13
|
+
|
|
14
|
+
// unaryDataCb builds a new unary request data callback.
|
|
15
|
+
function unaryDataCb(resolve: (data: Uint8Array) => void): DataCb {
|
|
16
|
+
return async (
|
|
17
|
+
data: Uint8Array
|
|
18
|
+
): Promise<boolean | void> => {
|
|
19
|
+
// resolve the promise
|
|
20
|
+
resolve(data)
|
|
21
|
+
// this is the last data we expect.
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// streamingDataCb builds a new streaming request data callback.
|
|
27
|
+
/*
|
|
28
|
+
function streamingDataCb(resolve: (data: Uint8Array) => void): DataCb {
|
|
29
|
+
return async (
|
|
30
|
+
data: Uint8Array
|
|
31
|
+
): Promise<boolean | void> => {
|
|
32
|
+
// TODO
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// writeClientStream registers the subscriber to write the client data stream.
|
|
38
|
+
function writeClientStream(call: ClientRPC, data: Observable<Uint8Array>) {
|
|
39
|
+
data.subscribe({
|
|
40
|
+
next(value) {
|
|
41
|
+
call.writeCallData(value)
|
|
42
|
+
},
|
|
43
|
+
error(err) {
|
|
44
|
+
call.close(err)
|
|
45
|
+
},
|
|
46
|
+
complete() {
|
|
47
|
+
call.writeCallData(new Uint8Array(0), true)
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// waitCallComplete handles the call complete promise.
|
|
53
|
+
function waitCallComplete(
|
|
54
|
+
call: ClientRPC,
|
|
55
|
+
resolve: (data: Uint8Array) => void,
|
|
56
|
+
reject: (err: Error) => void,
|
|
57
|
+
) {
|
|
58
|
+
call.waitComplete().catch(reject).finally(() => {
|
|
59
|
+
// ensure we resolve it if no data was ever returned.
|
|
60
|
+
resolve(new Uint8Array())
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Client implements the ts-proto Rpc interface with the drpcproto protocol.
|
|
65
|
+
export class Client implements TsProtoRpc {
|
|
66
|
+
// openConnFn is the open connection function.
|
|
67
|
+
// called when starting RPC.
|
|
68
|
+
private openConnFn: OpenStreamFunc
|
|
69
|
+
|
|
70
|
+
constructor(openConnFn: OpenStreamFunc) {
|
|
71
|
+
this.openConnFn = openConnFn
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// request starts a non-streaming request.
|
|
75
|
+
public async request(
|
|
76
|
+
service: string,
|
|
77
|
+
method: string,
|
|
78
|
+
data: Uint8Array
|
|
79
|
+
): Promise<Uint8Array> {
|
|
80
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
81
|
+
const dataCb = unaryDataCb(resolve)
|
|
82
|
+
this.startRpc(service, method, data, dataCb)
|
|
83
|
+
.then((call) => {
|
|
84
|
+
waitCallComplete(call, resolve, reject)
|
|
85
|
+
})
|
|
86
|
+
.catch(reject)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// clientStreamingRequest starts a client side streaming request.
|
|
91
|
+
public clientStreamingRequest(
|
|
92
|
+
service: string,
|
|
93
|
+
method: string,
|
|
94
|
+
data: Observable<Uint8Array>
|
|
95
|
+
): Promise<Uint8Array> {
|
|
96
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
97
|
+
const dataCb = unaryDataCb(resolve)
|
|
98
|
+
this.startRpc(service, method, null, dataCb)
|
|
99
|
+
.then((call) => {
|
|
100
|
+
writeClientStream(call, data)
|
|
101
|
+
waitCallComplete(call, resolve, reject)
|
|
102
|
+
})
|
|
103
|
+
.catch(reject)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// serverStreamingRequest starts a server-side streaming request.
|
|
108
|
+
public serverStreamingRequest(
|
|
109
|
+
service: string,
|
|
110
|
+
method: string,
|
|
111
|
+
data: Uint8Array
|
|
112
|
+
): Observable<Uint8Array> {
|
|
113
|
+
const pushServerData: Pushable<Uint8Array> = pushable()
|
|
114
|
+
const serverData = observableFrom(pushServerData)
|
|
115
|
+
const dataCb: DataCb = async (data: Uint8Array): Promise<boolean | void> => {
|
|
116
|
+
// push the message to the observable
|
|
117
|
+
pushServerData.push(data)
|
|
118
|
+
// expect more messages
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
this.startRpc(service, method, data, dataCb)
|
|
122
|
+
.then((call) => {
|
|
123
|
+
call.waitComplete().catch((err: Error) => {
|
|
124
|
+
pushServerData.throw(err)
|
|
125
|
+
}).finally(() => {
|
|
126
|
+
pushServerData.end()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
.catch(pushServerData.throw.bind(pushServerData))
|
|
130
|
+
return serverData
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// bidirectionalStreamingRequest starts a two-way streaming request.
|
|
134
|
+
public bidirectionalStreamingRequest(
|
|
135
|
+
service: string,
|
|
136
|
+
method: string,
|
|
137
|
+
data: Observable<Uint8Array>
|
|
138
|
+
): Observable<Uint8Array> {
|
|
139
|
+
const pushServerData: Pushable<Uint8Array> = pushable()
|
|
140
|
+
const serverData = observableFrom(pushServerData)
|
|
141
|
+
const dataCb: DataCb = async (data: Uint8Array): Promise<boolean | void> => {
|
|
142
|
+
// push the message to the observable
|
|
143
|
+
pushServerData.push(data)
|
|
144
|
+
// expect more messages
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
this.startRpc(service, method, null, dataCb)
|
|
148
|
+
.then((call) => {
|
|
149
|
+
writeClientStream(call, data)
|
|
150
|
+
call.waitComplete().catch((err: Error) => {
|
|
151
|
+
pushServerData.throw(err)
|
|
152
|
+
}).finally(() => {
|
|
153
|
+
pushServerData.end()
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
.catch(pushServerData.throw.bind(pushServerData))
|
|
157
|
+
return serverData
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// startRpc is a common utility function to begin a rpc call.
|
|
161
|
+
// throws any error starting the rpc call
|
|
162
|
+
private async startRpc(
|
|
163
|
+
rpcService: string,
|
|
164
|
+
rpcMethod: string,
|
|
165
|
+
data: Uint8Array | null,
|
|
166
|
+
dataCb: DataCb
|
|
167
|
+
): Promise<ClientRPC> {
|
|
168
|
+
const conn = await this.openConnFn()
|
|
169
|
+
const call = new ClientRPC(rpcService, rpcMethod, dataCb)
|
|
170
|
+
pipe(
|
|
171
|
+
conn,
|
|
172
|
+
parseLengthPrefixTransform(),
|
|
173
|
+
decodePacketSource,
|
|
174
|
+
call,
|
|
175
|
+
encodePacketSource,
|
|
176
|
+
prependLengthPrefixTransform(),
|
|
177
|
+
conn,
|
|
178
|
+
)
|
|
179
|
+
await call.writeCallStart(data || undefined)
|
|
180
|
+
return call
|
|
181
|
+
}
|
|
182
|
+
}
|
package/srpc/conn.go
ADDED
package/srpc/conn.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Stream } from '@libp2p/interfaces/connection'
|
|
2
|
+
import type { Duplex } from 'it-stream-types'
|
|
3
|
+
import { Components } from '@libp2p/interfaces/components'
|
|
4
|
+
import { MplexStreamMuxer } from '@libp2p/mplex'
|
|
5
|
+
import type { Stream as SRPCStream } from './stream'
|
|
6
|
+
import { Client } from './client'
|
|
7
|
+
|
|
8
|
+
// Conn implements a generic connection with a two-way stream.
|
|
9
|
+
export class Conn implements Duplex<Uint8Array> {
|
|
10
|
+
// muxer is the mplex stream muxer.
|
|
11
|
+
private muxer: MplexStreamMuxer
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
// see https://github.com/libp2p/js-libp2p-mplex/pull/179
|
|
15
|
+
this.muxer = new MplexStreamMuxer(new Components(), {
|
|
16
|
+
onIncomingStream: this.handleIncomingStream.bind(this),
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// sink returns the message sink.
|
|
21
|
+
get sink() {
|
|
22
|
+
return this.muxer.sink
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// source returns the outgoing message source.
|
|
26
|
+
get source() {
|
|
27
|
+
return this.muxer.source
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// streams returns the set of all ongoing streams.
|
|
31
|
+
get streams() {
|
|
32
|
+
return this.muxer.streams
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// buildClient builds a new client from the connection.
|
|
36
|
+
public buildClient(): Client {
|
|
37
|
+
return new Client(this.openStream.bind(this))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// openStream implements the client open stream function.
|
|
41
|
+
public async openStream(): Promise<SRPCStream> {
|
|
42
|
+
return this.muxer.newStream()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// handleIncomingStream handles an incoming stream.
|
|
46
|
+
private handleIncomingStream(strm: Stream) {
|
|
47
|
+
strm.abort(new Error('server -> client streams not implemented'))
|
|
48
|
+
}
|
|
49
|
+
}
|
package/srpc/errors.go
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import "errors"
|
|
4
|
+
|
|
5
|
+
var (
|
|
6
|
+
// ErrUnimplemented is returned if the RPC method was not implemented.
|
|
7
|
+
ErrUnimplemented = errors.New("unimplemented")
|
|
8
|
+
// ErrCompleted is returned if a message is received after the rpc was completed.
|
|
9
|
+
ErrCompleted = errors.New("unexpected packet after rpc was completed")
|
|
10
|
+
// ErrUnrecognizedPacket is returned if the packet type was not recognized.
|
|
11
|
+
ErrUnrecognizedPacket = errors.New("unrecognized packet type")
|
|
12
|
+
// ErrEmptyPacket is returned if nothing is specified in a packet.
|
|
13
|
+
ErrEmptyPacket = errors.New("invalid empty packet")
|
|
14
|
+
// ErrInvalidMessage indicates the message failed to parse.
|
|
15
|
+
ErrInvalidMessage = errors.New("invalid message")
|
|
16
|
+
// ErrEmptyMethodID is returned if the method id was empty.
|
|
17
|
+
ErrEmptyMethodID = errors.New("method id empty")
|
|
18
|
+
// ErrEmptyServiceID is returned if the service id was empty.
|
|
19
|
+
ErrEmptyServiceID = errors.New("service id empty")
|
|
20
|
+
)
|
package/srpc/handler.go
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
// Handler describes a SRPC call handler implementation.
|
|
4
|
+
type Handler interface {
|
|
5
|
+
// GetServiceID returns the ID of the service.
|
|
6
|
+
GetServiceID() string
|
|
7
|
+
// GetMethodIDs returns the list of methods for the service.
|
|
8
|
+
GetMethodIDs() []string
|
|
9
|
+
// InvokeMethod invokes the method matching the service & method ID.
|
|
10
|
+
// Returns false, nil if not found.
|
|
11
|
+
// If service string is empty, ignore it.
|
|
12
|
+
InvokeMethod(serviceID, methodID string, strm Stream) (bool, error)
|
|
13
|
+
}
|
package/srpc/index.ts
ADDED
package/srpc/message.go
ADDED
package/srpc/mux.go
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import "sync"
|
|
4
|
+
|
|
5
|
+
// Mux contains a set of <service, method> handlers.
|
|
6
|
+
type Mux interface {
|
|
7
|
+
// Register registers a new RPC method handler (service).
|
|
8
|
+
Register(handler Handler) error
|
|
9
|
+
// InvokeMethod invokes the method matching the service & method ID.
|
|
10
|
+
// Returns false, nil if not found.
|
|
11
|
+
// If service string is empty, ignore it.
|
|
12
|
+
InvokeMethod(serviceID, methodID string, strm Stream) (bool, error)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// muxMethods is a mapping from method id to handler.
|
|
16
|
+
type muxMethods map[string]Handler
|
|
17
|
+
|
|
18
|
+
// mux is the default implementation of Mux.
|
|
19
|
+
type mux struct {
|
|
20
|
+
// rmtx guards below fields
|
|
21
|
+
rmtx sync.RWMutex
|
|
22
|
+
// services contains a mapping from services to handlers.
|
|
23
|
+
services map[string]muxMethods
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// NewMux constructs a new Mux.
|
|
27
|
+
func NewMux() Mux {
|
|
28
|
+
return &mux{services: make(map[string]muxMethods)}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Register registers a new RPC method handler (service).
|
|
32
|
+
func (m *mux) Register(handler Handler) error {
|
|
33
|
+
serviceID := handler.GetServiceID()
|
|
34
|
+
methodIDs := handler.GetMethodIDs()
|
|
35
|
+
if serviceID == "" {
|
|
36
|
+
return ErrEmptyServiceID
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
m.rmtx.Lock()
|
|
40
|
+
defer m.rmtx.Unlock()
|
|
41
|
+
|
|
42
|
+
serviceMethods := m.services[serviceID]
|
|
43
|
+
if serviceMethods == nil {
|
|
44
|
+
serviceMethods = make(muxMethods)
|
|
45
|
+
m.services[serviceID] = serviceMethods
|
|
46
|
+
}
|
|
47
|
+
for _, methodID := range methodIDs {
|
|
48
|
+
if methodID != "" {
|
|
49
|
+
serviceMethods[methodID] = handler
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// InvokeMethod invokes the method matching the service & method ID.
|
|
57
|
+
// Returns false, nil if not found.
|
|
58
|
+
// If service string is empty, ignore it.
|
|
59
|
+
func (m *mux) InvokeMethod(serviceID, methodID string, strm Stream) (bool, error) {
|
|
60
|
+
var handler Handler
|
|
61
|
+
m.rmtx.RLock()
|
|
62
|
+
svcMethods := m.services[serviceID]
|
|
63
|
+
if svcMethods != nil {
|
|
64
|
+
handler = svcMethods[methodID]
|
|
65
|
+
}
|
|
66
|
+
m.rmtx.RUnlock()
|
|
67
|
+
|
|
68
|
+
if handler == nil {
|
|
69
|
+
return false, nil
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return handler.InvokeMethod(serviceID, methodID, strm)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// _ is a type assertion
|
|
76
|
+
var _ Mux = ((*mux)(nil))
|