starpc 0.24.0 → 0.25.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/srpc/channel.d.ts +38 -0
- package/dist/srpc/channel.js +164 -0
- package/dist/srpc/index.d.ts +1 -0
- package/dist/srpc/index.js +1 -0
- package/go.mod +1 -1
- package/go.sum +2 -0
- package/package.json +1 -1
- package/srpc/channel.ts +230 -0
- package/srpc/index.ts +6 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Sink, Source, Duplex } from 'it-stream-types';
|
|
2
|
+
export interface ChannelStreamMessage<T> {
|
|
3
|
+
from: string;
|
|
4
|
+
ack?: boolean;
|
|
5
|
+
opened?: boolean;
|
|
6
|
+
closed?: boolean;
|
|
7
|
+
error?: Error;
|
|
8
|
+
data?: T;
|
|
9
|
+
}
|
|
10
|
+
export type ChannelPort = MessagePort | {
|
|
11
|
+
tx: BroadcastChannel;
|
|
12
|
+
rx: BroadcastChannel;
|
|
13
|
+
};
|
|
14
|
+
export declare class ChannelStream<T> implements Duplex<AsyncGenerator<T>, Source<T>, Promise<void>> {
|
|
15
|
+
readonly channel: ChannelPort;
|
|
16
|
+
sink: Sink<Source<T>, Promise<void>>;
|
|
17
|
+
source: AsyncGenerator<T>;
|
|
18
|
+
private readonly _source;
|
|
19
|
+
private readonly localId;
|
|
20
|
+
private localOpen;
|
|
21
|
+
private remoteOpen;
|
|
22
|
+
readonly waitRemoteOpen: Promise<void>;
|
|
23
|
+
private _remoteOpen?;
|
|
24
|
+
private remoteAck;
|
|
25
|
+
readonly waitRemoteAck: Promise<void>;
|
|
26
|
+
private _remoteAck?;
|
|
27
|
+
get isAcked(): boolean;
|
|
28
|
+
get isOpen(): boolean;
|
|
29
|
+
constructor(localId: string, channel: ChannelPort, remoteOpen: boolean);
|
|
30
|
+
private postMessage;
|
|
31
|
+
close(error?: Error): void;
|
|
32
|
+
private onLocalOpened;
|
|
33
|
+
private onRemoteAcked;
|
|
34
|
+
private onRemoteOpened;
|
|
35
|
+
private _createSink;
|
|
36
|
+
private onMessage;
|
|
37
|
+
}
|
|
38
|
+
export declare function newBroadcastChannelStream<T>(id: string, readName: string, writeName: string, remoteOpen: boolean): ChannelStream<T>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { pushable } from 'it-pushable';
|
|
2
|
+
// ChannelStream implements a Stream over a BroadcastChannel duplex or MessagePort.
|
|
3
|
+
//
|
|
4
|
+
// NOTE: there is no way to tell if a BroadcastChannel or MessagePort is closed.
|
|
5
|
+
// This implementation sends a "closed" message when close() is called.
|
|
6
|
+
// However: if the remote is removed w/o closing cleanly, the stream will be left open!
|
|
7
|
+
export class ChannelStream {
|
|
8
|
+
// isAcked checks if the stream is acknowledged by the remote.
|
|
9
|
+
get isAcked() {
|
|
10
|
+
return this.remoteAck || false;
|
|
11
|
+
}
|
|
12
|
+
// isOpen checks if the stream is opened by the remote.
|
|
13
|
+
get isOpen() {
|
|
14
|
+
return this.remoteOpen || false;
|
|
15
|
+
}
|
|
16
|
+
// remoteOpen indicates if we know the remote has already opened the stream.
|
|
17
|
+
constructor(localId, channel, remoteOpen) {
|
|
18
|
+
this.localId = localId;
|
|
19
|
+
this.channel = channel;
|
|
20
|
+
this.sink = this._createSink();
|
|
21
|
+
this.localOpen = false;
|
|
22
|
+
this.remoteAck = remoteOpen;
|
|
23
|
+
this.remoteOpen = remoteOpen;
|
|
24
|
+
if (remoteOpen) {
|
|
25
|
+
this.waitRemoteOpen = Promise.resolve();
|
|
26
|
+
this.waitRemoteAck = Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.waitRemoteOpen = new Promise((resolve, reject) => {
|
|
30
|
+
this._remoteOpen = (err) => {
|
|
31
|
+
if (err) {
|
|
32
|
+
reject(err);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
resolve();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
this.waitRemoteOpen.catch(() => { });
|
|
40
|
+
this.waitRemoteAck = new Promise((resolve, reject) => {
|
|
41
|
+
this._remoteAck = (err) => {
|
|
42
|
+
if (err) {
|
|
43
|
+
reject(err);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
this.waitRemoteAck.catch(() => { });
|
|
51
|
+
}
|
|
52
|
+
const source = pushable({ objectMode: true });
|
|
53
|
+
this.source = source;
|
|
54
|
+
this._source = source;
|
|
55
|
+
const onMessage = this.onMessage.bind(this);
|
|
56
|
+
if (channel instanceof MessagePort) {
|
|
57
|
+
// MessagePort
|
|
58
|
+
channel.onmessage = onMessage;
|
|
59
|
+
channel.start();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// BroadcastChannel
|
|
63
|
+
channel.rx.onmessage = onMessage;
|
|
64
|
+
}
|
|
65
|
+
this.postMessage({ ack: true });
|
|
66
|
+
}
|
|
67
|
+
// postMessage writes a message to the stream.
|
|
68
|
+
postMessage(msg) {
|
|
69
|
+
msg.from = this.localId;
|
|
70
|
+
if (this.channel instanceof MessagePort) {
|
|
71
|
+
this.channel.postMessage(msg);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.channel.tx.postMessage(msg);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// close closes the broadcast channels.
|
|
78
|
+
close(error) {
|
|
79
|
+
// write a message to indicate the stream is now closed.
|
|
80
|
+
this.postMessage({ closed: true, error });
|
|
81
|
+
// close channels
|
|
82
|
+
if (this.channel instanceof MessagePort) {
|
|
83
|
+
this.channel.close();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.channel.tx.close();
|
|
87
|
+
this.channel.rx.close();
|
|
88
|
+
}
|
|
89
|
+
if (!this.remoteOpen && this._remoteOpen) {
|
|
90
|
+
this._remoteOpen(error || new Error('closed'));
|
|
91
|
+
}
|
|
92
|
+
if (!this.remoteAck && this._remoteAck) {
|
|
93
|
+
this._remoteAck(error || new Error('closed'));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// onLocalOpened indicates the local side has opened the read stream.
|
|
97
|
+
onLocalOpened() {
|
|
98
|
+
if (!this.localOpen) {
|
|
99
|
+
this.localOpen = true;
|
|
100
|
+
this.postMessage({ opened: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// onRemoteAcked indicates the remote side has acked the stream.
|
|
104
|
+
onRemoteAcked() {
|
|
105
|
+
if (!this.remoteAck) {
|
|
106
|
+
this.remoteAck = true;
|
|
107
|
+
if (this._remoteAck) {
|
|
108
|
+
this._remoteAck();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// onRemoteOpened indicates the remote side has opened the read stream.
|
|
113
|
+
onRemoteOpened() {
|
|
114
|
+
if (!this.remoteOpen) {
|
|
115
|
+
this.remoteOpen = true;
|
|
116
|
+
if (this._remoteOpen) {
|
|
117
|
+
this._remoteOpen();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
_createSink() {
|
|
122
|
+
return async (source) => {
|
|
123
|
+
// make sure the remote is open before we send any data.
|
|
124
|
+
await this.waitRemoteAck;
|
|
125
|
+
this.onLocalOpened();
|
|
126
|
+
await this.waitRemoteOpen;
|
|
127
|
+
try {
|
|
128
|
+
for await (const msg of source) {
|
|
129
|
+
this.postMessage({ data: msg });
|
|
130
|
+
}
|
|
131
|
+
this.postMessage({ closed: true });
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.postMessage({ closed: true, error: error });
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
onMessage(ev) {
|
|
139
|
+
const msg = ev.data;
|
|
140
|
+
if (!msg || msg.from === this.localId || !msg.from) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (msg.ack || msg.opened) {
|
|
144
|
+
this.onRemoteAcked();
|
|
145
|
+
}
|
|
146
|
+
if (msg.opened) {
|
|
147
|
+
this.onRemoteOpened();
|
|
148
|
+
}
|
|
149
|
+
const { data, closed, error: err } = msg;
|
|
150
|
+
if (data) {
|
|
151
|
+
this._source.push(data);
|
|
152
|
+
}
|
|
153
|
+
if (err) {
|
|
154
|
+
this._source.end(err);
|
|
155
|
+
}
|
|
156
|
+
else if (closed) {
|
|
157
|
+
this._source.end();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// newBroadcastChannelStream constructs a ChannelStream with a channel name.
|
|
162
|
+
export function newBroadcastChannelStream(id, readName, writeName, remoteOpen) {
|
|
163
|
+
return new ChannelStream(id, { tx: new BroadcastChannel(writeName), rx: new BroadcastChannel(readName) }, remoteOpen);
|
|
164
|
+
}
|
package/dist/srpc/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { Handler, InvokeFn, MethodMap, StaticHandler, createHandler, } from './h
|
|
|
8
8
|
export { MethodProto, createInvokeFn } from './invoker.js';
|
|
9
9
|
export { Packet, CallStart, CallData } from './rpcproto.pb.js';
|
|
10
10
|
export { Mux, StaticMux, LookupMethod, createMux } from './mux.js';
|
|
11
|
+
export { ChannelStreamMessage, ChannelPort, ChannelStream, newBroadcastChannelStream, } from './channel.js';
|
|
11
12
|
export { BroadcastChannelDuplex, BroadcastChannelConn, newBroadcastChannelDuplex, } from './broadcast-channel.js';
|
|
12
13
|
export { MessagePortDuplex, MessagePortConn, newMessagePortDuplex, } from './message-port.js';
|
|
13
14
|
export { MessageDefinition, DecodeMessageTransform, buildDecodeMessageTransform, EncodeMessageTransform, buildEncodeMessageTransform, memoProto, memoProtoDecode, } from './message.js';
|
package/dist/srpc/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { StaticHandler, createHandler, } from './handler.js';
|
|
|
7
7
|
export { createInvokeFn } from './invoker.js';
|
|
8
8
|
export { Packet, CallStart, CallData } from './rpcproto.pb.js';
|
|
9
9
|
export { StaticMux, createMux } from './mux.js';
|
|
10
|
+
export { ChannelStream, newBroadcastChannelStream, } from './channel.js';
|
|
10
11
|
export { BroadcastChannelDuplex, BroadcastChannelConn, newBroadcastChannelDuplex, } from './broadcast-channel.js';
|
|
11
12
|
export { MessagePortDuplex, MessagePortConn, newMessagePortDuplex, } from './message-port.js';
|
|
12
13
|
export { buildDecodeMessageTransform, buildEncodeMessageTransform, memoProto, memoProtoDecode, } from './message.js';
|
package/go.mod
CHANGED
|
@@ -9,7 +9,7 @@ require (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
require (
|
|
12
|
-
github.com/aperturerobotics/util v1.13.
|
|
12
|
+
github.com/aperturerobotics/util v1.13.5 // latest
|
|
13
13
|
github.com/libp2p/go-libp2p v0.32.2 // latest
|
|
14
14
|
github.com/libp2p/go-yamux/v4 v4.0.2-0.20240206065824-7222fbc3459d // master
|
|
15
15
|
github.com/sirupsen/logrus v1.9.3 // latest
|
package/go.sum
CHANGED
|
@@ -2,6 +2,8 @@ github.com/aperturerobotics/util v1.13.2 h1:MTe8MO+Tfo9d3Z4Zi6Akm//rIymy5gyoAOIE
|
|
|
2
2
|
github.com/aperturerobotics/util v1.13.2/go.mod h1:d84OAQAGXCpl7JOBstnal91Lm6nKgk+vBgtHPgxBYrQ=
|
|
3
3
|
github.com/aperturerobotics/util v1.13.3 h1:N4JXKYql0R/A2hMuV79SZ2vaftf8tiN8DUJ8sGc7Rso=
|
|
4
4
|
github.com/aperturerobotics/util v1.13.3/go.mod h1:JdziNd9tR6lWqc9bSMIe1At8Gagrg986rmtZuPCv6+w=
|
|
5
|
+
github.com/aperturerobotics/util v1.13.5 h1:g8Q9VKBUYR5Nu5Ee/ZygVbe0JkGXeNq1fBkT0ipLBMY=
|
|
6
|
+
github.com/aperturerobotics/util v1.13.5/go.mod h1:8AfpGb9RJqUItLBb5ec3sprpl9swYyHlgOw0HzkE+S8=
|
|
5
7
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
6
8
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
7
9
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
package/package.json
CHANGED
package/srpc/channel.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { Sink, Source, Duplex } from 'it-stream-types'
|
|
2
|
+
import { pushable, Pushable } from 'it-pushable'
|
|
3
|
+
|
|
4
|
+
// ChannelStreamMessage is a message sent over the stream.
|
|
5
|
+
export interface ChannelStreamMessage<T> {
|
|
6
|
+
// from indicates who sent the message.
|
|
7
|
+
from: string
|
|
8
|
+
// ack indicates a remote joined the stream.
|
|
9
|
+
ack?: boolean
|
|
10
|
+
// opened indicates the remote has opened the stream.
|
|
11
|
+
opened?: boolean
|
|
12
|
+
// closed indicates the stream is closed.
|
|
13
|
+
closed?: boolean
|
|
14
|
+
// error indicates the stream has an error.
|
|
15
|
+
error?: Error
|
|
16
|
+
// data is any message data.
|
|
17
|
+
data?: T
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Channel represents a channel we can open a stream over.
|
|
21
|
+
export type ChannelPort = MessagePort | { tx: BroadcastChannel; rx: BroadcastChannel }
|
|
22
|
+
|
|
23
|
+
// ChannelStream implements a Stream over a BroadcastChannel duplex or MessagePort.
|
|
24
|
+
//
|
|
25
|
+
// NOTE: there is no way to tell if a BroadcastChannel or MessagePort is closed.
|
|
26
|
+
// This implementation sends a "closed" message when close() is called.
|
|
27
|
+
// However: if the remote is removed w/o closing cleanly, the stream will be left open!
|
|
28
|
+
export class ChannelStream<T>
|
|
29
|
+
implements Duplex<AsyncGenerator<T>, Source<T>, Promise<void>>
|
|
30
|
+
{
|
|
31
|
+
// channel is the read/write channel.
|
|
32
|
+
public readonly channel: ChannelPort
|
|
33
|
+
// sink is the sink for incoming messages.
|
|
34
|
+
public sink: Sink<Source<T>, Promise<void>>
|
|
35
|
+
// source is the source for outgoing messages.
|
|
36
|
+
public source: AsyncGenerator<T>
|
|
37
|
+
// _source emits incoming data to the source.
|
|
38
|
+
private readonly _source: {
|
|
39
|
+
push: (val: T) => void
|
|
40
|
+
end: (err?: Error) => void
|
|
41
|
+
}
|
|
42
|
+
// localId is the local identifier
|
|
43
|
+
private readonly localId: string
|
|
44
|
+
// localOpen indicates the local side has opened the stream.
|
|
45
|
+
private localOpen: boolean
|
|
46
|
+
// remoteOpen indicates the remote side has opened the stream.
|
|
47
|
+
private remoteOpen: boolean
|
|
48
|
+
// waitRemoteOpen indicates the remote side has opened the stream.
|
|
49
|
+
public readonly waitRemoteOpen: Promise<void>
|
|
50
|
+
// _remoteOpen fulfills the waitRemoteOpen promise.
|
|
51
|
+
private _remoteOpen?: (err?: Error) => void
|
|
52
|
+
// remoteAck indicates the remote side has acked the stream.
|
|
53
|
+
private remoteAck: boolean
|
|
54
|
+
// waitRemoteAck indicates the remote side has opened the stream.
|
|
55
|
+
public readonly waitRemoteAck: Promise<void>
|
|
56
|
+
// _remoteAck fulfills the waitRemoteAck promise.
|
|
57
|
+
private _remoteAck?: (err?: Error) => void
|
|
58
|
+
|
|
59
|
+
// isAcked checks if the stream is acknowledged by the remote.
|
|
60
|
+
public get isAcked() {
|
|
61
|
+
return this.remoteAck || false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// isOpen checks if the stream is opened by the remote.
|
|
65
|
+
public get isOpen() {
|
|
66
|
+
return this.remoteOpen || false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// remoteOpen indicates if we know the remote has already opened the stream.
|
|
70
|
+
constructor(localId: string, channel: ChannelPort, remoteOpen: boolean) {
|
|
71
|
+
this.localId = localId
|
|
72
|
+
this.channel = channel
|
|
73
|
+
this.sink = this._createSink()
|
|
74
|
+
|
|
75
|
+
this.localOpen = false
|
|
76
|
+
this.remoteAck = remoteOpen
|
|
77
|
+
this.remoteOpen = remoteOpen
|
|
78
|
+
if (remoteOpen) {
|
|
79
|
+
this.waitRemoteOpen = Promise.resolve()
|
|
80
|
+
this.waitRemoteAck = Promise.resolve()
|
|
81
|
+
} else {
|
|
82
|
+
this.waitRemoteOpen = new Promise<void>((resolve, reject) => {
|
|
83
|
+
this._remoteOpen = (err?: Error) => {
|
|
84
|
+
if (err) {
|
|
85
|
+
reject(err)
|
|
86
|
+
} else {
|
|
87
|
+
resolve()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
this.waitRemoteOpen.catch(() => {})
|
|
92
|
+
this.waitRemoteAck = new Promise<void>((resolve, reject) => {
|
|
93
|
+
this._remoteAck = (err?: Error) => {
|
|
94
|
+
if (err) {
|
|
95
|
+
reject(err)
|
|
96
|
+
} else {
|
|
97
|
+
resolve()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
this.waitRemoteAck.catch(() => {})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const source: Pushable<T> = pushable({ objectMode: true })
|
|
105
|
+
this.source = source
|
|
106
|
+
this._source = source
|
|
107
|
+
|
|
108
|
+
const onMessage = this.onMessage.bind(this)
|
|
109
|
+
if (channel instanceof MessagePort) {
|
|
110
|
+
// MessagePort
|
|
111
|
+
channel.onmessage = onMessage
|
|
112
|
+
channel.start()
|
|
113
|
+
} else {
|
|
114
|
+
// BroadcastChannel
|
|
115
|
+
channel.rx.onmessage = onMessage
|
|
116
|
+
}
|
|
117
|
+
this.postMessage({ ack: true })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// postMessage writes a message to the stream.
|
|
121
|
+
private postMessage(msg: Partial<ChannelStreamMessage<T>>) {
|
|
122
|
+
msg.from = this.localId
|
|
123
|
+
if (this.channel instanceof MessagePort) {
|
|
124
|
+
this.channel.postMessage(msg)
|
|
125
|
+
} else {
|
|
126
|
+
this.channel.tx.postMessage(msg)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// close closes the broadcast channels.
|
|
131
|
+
public close(error?: Error) {
|
|
132
|
+
// write a message to indicate the stream is now closed.
|
|
133
|
+
this.postMessage({ closed: true, error })
|
|
134
|
+
// close channels
|
|
135
|
+
if (this.channel instanceof MessagePort) {
|
|
136
|
+
this.channel.close()
|
|
137
|
+
} else {
|
|
138
|
+
this.channel.tx.close()
|
|
139
|
+
this.channel.rx.close()
|
|
140
|
+
}
|
|
141
|
+
if (!this.remoteOpen && this._remoteOpen) {
|
|
142
|
+
this._remoteOpen(error || new Error('closed'))
|
|
143
|
+
}
|
|
144
|
+
if (!this.remoteAck && this._remoteAck) {
|
|
145
|
+
this._remoteAck(error || new Error('closed'))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// onLocalOpened indicates the local side has opened the read stream.
|
|
150
|
+
private onLocalOpened() {
|
|
151
|
+
if (!this.localOpen) {
|
|
152
|
+
this.localOpen = true
|
|
153
|
+
this.postMessage({ opened: true })
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// onRemoteAcked indicates the remote side has acked the stream.
|
|
158
|
+
private onRemoteAcked() {
|
|
159
|
+
if (!this.remoteAck) {
|
|
160
|
+
this.remoteAck = true
|
|
161
|
+
if (this._remoteAck) {
|
|
162
|
+
this._remoteAck()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// onRemoteOpened indicates the remote side has opened the read stream.
|
|
168
|
+
private onRemoteOpened() {
|
|
169
|
+
if (!this.remoteOpen) {
|
|
170
|
+
this.remoteOpen = true
|
|
171
|
+
if (this._remoteOpen) {
|
|
172
|
+
this._remoteOpen()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private _createSink(): Sink<Source<T>, Promise<void>> {
|
|
178
|
+
return async (source: Source<T>) => {
|
|
179
|
+
// make sure the remote is open before we send any data.
|
|
180
|
+
await this.waitRemoteAck
|
|
181
|
+
this.onLocalOpened()
|
|
182
|
+
await this.waitRemoteOpen
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
for await (const msg of source) {
|
|
186
|
+
this.postMessage({ data: msg })
|
|
187
|
+
}
|
|
188
|
+
this.postMessage({ closed: true })
|
|
189
|
+
} catch (error) {
|
|
190
|
+
this.postMessage({ closed: true, error: error as Error })
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private onMessage(ev: MessageEvent<ChannelStreamMessage<T>>) {
|
|
196
|
+
const msg = ev.data
|
|
197
|
+
if (!msg || msg.from === this.localId || !msg.from) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
if (msg.ack || msg.opened) {
|
|
201
|
+
this.onRemoteAcked()
|
|
202
|
+
}
|
|
203
|
+
if (msg.opened) {
|
|
204
|
+
this.onRemoteOpened()
|
|
205
|
+
}
|
|
206
|
+
const { data, closed, error: err } = msg
|
|
207
|
+
if (data) {
|
|
208
|
+
this._source.push(data)
|
|
209
|
+
}
|
|
210
|
+
if (err) {
|
|
211
|
+
this._source.end(err)
|
|
212
|
+
} else if (closed) {
|
|
213
|
+
this._source.end()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// newBroadcastChannelStream constructs a ChannelStream with a channel name.
|
|
219
|
+
export function newBroadcastChannelStream<T>(
|
|
220
|
+
id: string,
|
|
221
|
+
readName: string,
|
|
222
|
+
writeName: string,
|
|
223
|
+
remoteOpen: boolean,
|
|
224
|
+
): ChannelStream<T> {
|
|
225
|
+
return new ChannelStream<T>(
|
|
226
|
+
id,
|
|
227
|
+
{ tx: new BroadcastChannel(writeName), rx: new BroadcastChannel(readName) },
|
|
228
|
+
remoteOpen,
|
|
229
|
+
)
|
|
230
|
+
}
|
package/srpc/index.ts
CHANGED
|
@@ -19,6 +19,12 @@ export {
|
|
|
19
19
|
export { MethodProto, createInvokeFn } from './invoker.js'
|
|
20
20
|
export { Packet, CallStart, CallData } from './rpcproto.pb.js'
|
|
21
21
|
export { Mux, StaticMux, LookupMethod, createMux } from './mux.js'
|
|
22
|
+
export {
|
|
23
|
+
ChannelStreamMessage,
|
|
24
|
+
ChannelPort,
|
|
25
|
+
ChannelStream,
|
|
26
|
+
newBroadcastChannelStream,
|
|
27
|
+
} from './channel.js'
|
|
22
28
|
export {
|
|
23
29
|
BroadcastChannelDuplex,
|
|
24
30
|
BroadcastChannelConn,
|