1
1
import { EventEmitter } from "events" ;
2
- import { isPromise } from "./util" ;
2
+ import { isPromise , EventCallback } from "./util" ;
3
3
4
4
// tslint:disable no-any
5
5
@@ -64,6 +64,22 @@ export abstract class ClientProxy<T extends ClientServerProxy> extends EventEmit
64
64
return this ;
65
65
}
66
66
67
+ /**
68
+ * Bind the event locally and ensure the event is bound on the server.
69
+ */
70
+ public addListener ( event : string , listener : ( ...args : any [ ] ) => void ) : this {
71
+ this . catch ( this . proxy . bindDelayedEvent ( event ) ) ;
72
+
73
+ return super . on ( event , listener ) ;
74
+ }
75
+
76
+ /**
77
+ * Alias for `addListener`.
78
+ */
79
+ public on ( event : string , listener : ( ...args : any [ ] ) => void ) : this {
80
+ return this . addListener ( event , listener ) ;
81
+ }
82
+
67
83
/**
68
84
* Original promise for the server proxy. Can be used to be passed as an
69
85
* argument.
@@ -89,9 +105,9 @@ export abstract class ClientProxy<T extends ClientServerProxy> extends EventEmit
89
105
? unpromisify ( this . _proxyPromise )
90
106
: this . _proxyPromise ;
91
107
if ( this . bindEvents ) {
92
- this . catch ( this . proxy . onEvent ( ( event , ...args ) : void => {
108
+ this . proxy . onEvent ( ( event , ...args ) : void => {
93
109
this . emit ( event , ...args ) ;
94
- } ) ) ;
110
+ } ) ;
95
111
}
96
112
97
113
return this . _proxy ;
@@ -114,8 +130,27 @@ export abstract class ClientProxy<T extends ClientServerProxy> extends EventEmit
114
130
}
115
131
}
116
132
133
+ export interface ServerProxyOptions < T > {
134
+ /**
135
+ * The events to bind immediately.
136
+ */
137
+ bindEvents : string [ ] ;
138
+ /**
139
+ * Events that signal the proxy is done.
140
+ */
141
+ doneEvents : string [ ] ;
142
+ /**
143
+ * Events that should only be bound when asked
144
+ */
145
+ delayedEvents ?: string [ ] ;
146
+ /**
147
+ * Whatever is emitting events (stream, child process, etc).
148
+ */
149
+ instance : T ;
150
+ }
151
+
117
152
/**
118
- * Proxy to the actual instance on the server. Every method must only accept
153
+ * The actual proxy instance on the server. Every method must only accept
119
154
* serializable arguments and must return promises with serializable values.
120
155
*
121
156
* If a proxy itself has proxies on creation (like how ChildProcess has stdin),
@@ -125,17 +160,51 @@ export abstract class ClientProxy<T extends ClientServerProxy> extends EventEmit
125
160
* Events listeners are added client-side (since all events automatically
126
161
* forward to the client), so onDone and onEvent do not need to be asynchronous.
127
162
*/
128
- export interface ServerProxy {
163
+ export abstract class ServerProxy < T extends EventEmitter = EventEmitter > {
164
+ public readonly instance : T ;
165
+
166
+ private readonly callbacks = < EventCallback [ ] > [ ] ;
167
+
168
+ public constructor ( private readonly options : ServerProxyOptions < T > ) {
169
+ this . instance = options . instance ;
170
+ }
171
+
129
172
/**
130
173
* Dispose the proxy.
131
174
*/
132
- dispose ( ) : Promise < void > ;
175
+ public async dispose ( ) : Promise < void > {
176
+ this . instance . removeAllListeners ( ) ;
177
+ }
133
178
134
179
/**
135
180
* This is used instead of an event to force it to be implemented since there
136
181
* would be no guarantee the implementation would remember to emit the event.
137
182
*/
138
- onDone ( cb : ( ) => void ) : void ;
183
+ public onDone ( cb : ( ) => void ) : void {
184
+ this . options . doneEvents . forEach ( ( event ) => {
185
+ this . instance . on ( event , cb ) ;
186
+ } ) ;
187
+ }
188
+
189
+ /**
190
+ * Bind an event that will not fire without first binding it and shouldn't be
191
+ * bound immediately.
192
+
193
+ * For example, binding to `data` switches a stream to flowing mode, so we
194
+ * don't want to do it until we're asked. Otherwise something like `pipe`
195
+ * won't work because potentially some or all of the data will already have
196
+ * been flushed out.
197
+ */
198
+ public async bindDelayedEvent ( event : string ) : Promise < void > {
199
+ if ( this . options . delayedEvents
200
+ && this . options . delayedEvents . includes ( event )
201
+ && ! this . options . bindEvents . includes ( event ) ) {
202
+ this . options . bindEvents . push ( event ) ;
203
+ this . callbacks . forEach ( ( cb ) => {
204
+ this . instance . on ( event , ( ...args : any [ ] ) => cb ( event , ...args ) ) ;
205
+ } ) ;
206
+ }
207
+ }
139
208
140
209
/**
141
210
* Listen to all possible events. On the client, this is to reduce boilerplate
@@ -147,15 +216,20 @@ export interface ServerProxy {
147
216
*
148
217
* This cannot be async because then we can bind to the events too late.
149
218
*/
150
- // tslint:disable-next-line no-any
151
- onEvent ( cb : ( event : string , ...args : any [ ] ) => void ) : void ;
219
+ public onEvent ( cb : EventCallback ) : void {
220
+ this . callbacks . push ( cb ) ;
221
+ this . options . bindEvents . forEach ( ( event ) => {
222
+ this . instance . on ( event , ( ...args : any [ ] ) => cb ( event , ...args ) ) ;
223
+ } ) ;
224
+ }
152
225
}
153
226
154
227
/**
155
228
* A server-side proxy stored on the client. The proxy ID only exists on the
156
- * client-side version of the server proxy.
229
+ * client-side version of the server proxy. The event listeners are handled by
230
+ * the client and the remaining methods are proxied to the server.
157
231
*/
158
- export interface ClientServerProxy extends ServerProxy {
232
+ export interface ClientServerProxy < T extends EventEmitter = EventEmitter > extends ServerProxy < T > {
159
233
proxyId : number | Module ;
160
234
}
161
235
0 commit comments