Skip to content

Commit 20232c9

Browse files
committed
Allow passing proxies back from client to server
This makes things like piping streams possible.
1 parent efa98bf commit 20232c9

File tree

13 files changed

+228
-70
lines changed

13 files changed

+228
-70
lines changed

packages/protocol/src/browser/client.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { promisify } from "util";
44
import { Emitter } from "@coder/events";
55
import { logger, field } from "@coder/logger";
66
import { ReadWriteConnection, InitData, SharedProcessData } from "../common/connection";
7-
import { Module, ServerProxy } from "../common/proxy";
7+
import { ClientServerProxy, Module, ServerProxy } from "../common/proxy";
88
import { argumentToProto, protoToArgument, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
99
import { Argument, Ping, ServerMessage, ClientMessage, Method, Event, Callback } from "../proto";
1010
import { FsModule, ChildProcessModule, NetModule, NodePtyModule, SpdlogModule, TrashModule } from "./modules";
@@ -224,7 +224,11 @@ export class Client {
224224
field("method", method),
225225
]);
226226

227-
proxyMessage.setArgsList(args.map((a) => argumentToProto(a, storeCallback)));
227+
proxyMessage.setArgsList(args.map((a) => argumentToProto<ClientServerProxy>(
228+
a,
229+
storeCallback,
230+
(p) => p.proxyId,
231+
)));
228232

229233
const clientMessage = new ClientMessage();
230234
clientMessage.setMethod(message);
@@ -429,7 +433,7 @@ export class Client {
429433
/**
430434
* Return a proxy that makes remote calls.
431435
*/
432-
private createProxy<T>(proxyId: number | Module, promise: Promise<any> = Promise.resolve()): T {
436+
private createProxy<T extends ClientServerProxy>(proxyId: number | Module, promise: Promise<any> = Promise.resolve()): T {
433437
logger.trace(() => [
434438
"creating proxy",
435439
field("proxyId", proxyId),
@@ -449,7 +453,7 @@ export class Client {
449453
cb(event.event, ...event.args);
450454
});
451455
},
452-
}, {
456+
} as ClientServerProxy, {
453457
get: (target: any, name: string): any => {
454458
// When resolving a promise with a proxy, it will check for "then".
455459
if (name === "then") {

packages/protocol/src/browser/modules/child_process.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,24 @@ import * as cp from "child_process";
22
import * as net from "net";
33
import * as stream from "stream";
44
import { callbackify } from "util";
5-
import { ClientProxy } from "../../common/proxy";
6-
import { ChildProcessModuleProxy, ChildProcessProxy, ChildProcessProxies } from "../../node/modules/child_process";
7-
import { Readable, Writable } from "./stream";
5+
import { ClientProxy, Module } from "../../common/proxy";
6+
import { ChildProcessModuleProxy, ChildProcessProxy } from "../../node/modules/child_process";
7+
import { ClientWritableProxy, ClientReadableProxy, Readable, Writable } from "./stream";
88

99
// tslint:disable completed-docs
1010

11-
export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.ChildProcess {
11+
export interface ClientChildProcessProxy extends ChildProcessProxy {
12+
proxyId: number | Module;
13+
}
14+
15+
export interface ClientChildProcessProxies {
16+
childProcess: ClientChildProcessProxy;
17+
stdin?: ClientWritableProxy | null;
18+
stdout?: ClientReadableProxy | null;
19+
stderr?: ClientReadableProxy | null;
20+
}
21+
22+
export class ChildProcess extends ClientProxy<ClientChildProcessProxy> implements cp.ChildProcess {
1223
public readonly stdin: stream.Writable;
1324
public readonly stdout: stream.Readable;
1425
public readonly stderr: stream.Readable;
@@ -18,7 +29,7 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
1829
private _killed: boolean = false;
1930
private _pid = -1;
2031

21-
public constructor(proxyPromises: Promise<ChildProcessProxies>) {
32+
public constructor(proxyPromises: Promise<ClientChildProcessProxies>) {
2233
super(proxyPromises.then((p) => p.childProcess));
2334
this.stdin = new Writable(proxyPromises.then((p) => p.stdin!));
2435
this.stdout = new Readable(proxyPromises.then((p) => p.stdout!));
@@ -99,8 +110,15 @@ export class ChildProcess extends ClientProxy<ChildProcessProxy> implements cp.C
99110
}
100111
}
101112

113+
interface ClientChildProcessModuleProxy extends ChildProcessModuleProxy {
114+
proxyId: number | Module;
115+
exec(command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void)): Promise<ClientChildProcessProxies>;
116+
fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ClientChildProcessProxies>;
117+
spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ClientChildProcessProxies>;
118+
}
119+
102120
export class ChildProcessModule {
103-
public constructor(private readonly proxy: ChildProcessModuleProxy) {}
121+
public constructor(private readonly proxy: ClientChildProcessModuleProxy) {}
104122

105123
public exec = (
106124
command: string,

packages/protocol/src/browser/modules/fs.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as fs from "fs";
22
import { callbackify } from "util";
3-
import { ClientProxy, Batch } from "../../common/proxy";
3+
import { Batch, ClientProxy, Module } from "../../common/proxy";
44
import { IEncodingOptions, IEncodingOptionsCallback } from "../../common/util";
55
import { FsModuleProxy, ReadStreamProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
66
import { Readable, Writable } from "./stream";
@@ -38,7 +38,11 @@ class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.P
3838
}
3939
}
4040

41-
class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
41+
interface ClientWatcherProxy extends WatcherProxy {
42+
proxyId: number | Module;
43+
}
44+
45+
class Watcher extends ClientProxy<ClientWatcherProxy> implements fs.FSWatcher {
4246
public close(): void {
4347
this.catch(this.proxy.close());
4448
}
@@ -48,7 +52,11 @@ class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
4852
}
4953
}
5054

51-
class ReadStream extends Readable<ReadStreamProxy> implements fs.ReadStream {
55+
interface ClientReadStreamProxy extends ReadStreamProxy {
56+
proxyId: number | Module;
57+
}
58+
59+
class ReadStream extends Readable<ClientReadStreamProxy> implements fs.ReadStream {
5260
public get bytesRead(): number {
5361
throw new Error("not implemented");
5462
}
@@ -62,7 +70,11 @@ class ReadStream extends Readable<ReadStreamProxy> implements fs.ReadStream {
6270
}
6371
}
6472

65-
class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
73+
interface ClientWriteStreamProxy extends WriteStreamProxy {
74+
proxyId: number | Module;
75+
}
76+
77+
class WriteStream extends Writable<ClientWriteStreamProxy> implements fs.WriteStream {
6678
public get bytesWritten(): number {
6779
throw new Error("not implemented");
6880
}
@@ -76,12 +88,19 @@ class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
7688
}
7789
}
7890

91+
interface ClientFsModuleProxy extends FsModuleProxy {
92+
proxyId: number | Module;
93+
createReadStream(path: fs.PathLike, options?: any): Promise<ClientReadStreamProxy>;
94+
createWriteStream(path: fs.PathLike, options?: any): Promise<ClientWriteStreamProxy>;
95+
watch(filename: fs.PathLike, options?: IEncodingOptions): Promise<ClientWatcherProxy>;
96+
}
97+
7998
export class FsModule {
8099
private readonly statBatch: StatBatch;
81100
private readonly lstatBatch: LstatBatch;
82101
private readonly readdirBatch: ReaddirBatch;
83102

84-
public constructor(private readonly proxy: FsModuleProxy) {
103+
public constructor(private readonly proxy: ClientFsModuleProxy) {
85104
this.statBatch = new StatBatch(this.proxy);
86105
this.lstatBatch = new LstatBatch(this.proxy);
87106
this.readdirBatch = new ReaddirBatch(this.proxy);

packages/protocol/src/browser/modules/net.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import * as net from "net";
22
import { callbackify } from "util";
3-
import { ClientProxy } from "../../common/proxy";
3+
import { ClientProxy, Module } from "../../common/proxy";
44
import { NetModuleProxy, NetServerProxy, NetSocketProxy } from "../../node/modules/net";
55
import { Duplex } from "./stream";
66

77
// tslint:disable completed-docs
88

9-
export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
9+
interface ClientNetSocketProxy extends NetSocketProxy {
10+
proxyId: number | Module;
11+
}
12+
13+
export class Socket extends Duplex<ClientNetSocketProxy> implements net.Socket {
1014
private _connecting: boolean = false;
1115
private _destroyed: boolean = false;
1216

13-
public constructor(proxyPromise: Promise<NetSocketProxy> | NetSocketProxy, connecting?: boolean) {
17+
public constructor(proxyPromise: Promise<ClientNetSocketProxy> | ClientNetSocketProxy, connecting?: boolean) {
1418
super(proxyPromise);
1519
if (connecting) {
1620
this._connecting = connecting;
@@ -126,12 +130,17 @@ export class Socket extends Duplex<NetSocketProxy> implements net.Socket {
126130
}
127131
}
128132

129-
export class Server extends ClientProxy<NetServerProxy> implements net.Server {
133+
interface ClientNetServerProxy extends NetServerProxy {
134+
proxyId: number | Module;
135+
onConnection(cb: (proxy: ClientNetSocketProxy) => void): Promise<void>;
136+
}
137+
138+
export class Server extends ClientProxy<ClientNetServerProxy> implements net.Server {
130139
private socketId = 0;
131140
private readonly sockets = new Map<number, net.Socket>();
132141
private _listening: boolean = false;
133142

134-
public constructor(proxyPromise: Promise<NetServerProxy> | NetServerProxy) {
143+
public constructor(proxyPromise: Promise<ClientNetServerProxy> | ClientNetServerProxy) {
135144
super(proxyPromise);
136145

137146
this.catch(this.proxy.onConnection((socketProxy) => {
@@ -208,11 +217,18 @@ export class Server extends ClientProxy<NetServerProxy> implements net.Server {
208217

209218
type NodeNet = typeof net;
210219

220+
interface ClientNetModuleProxy extends NetModuleProxy {
221+
proxyId: number | Module;
222+
createSocket(options?: net.SocketConstructorOpts): Promise<ClientNetSocketProxy>;
223+
createConnection(target: string | number | net.NetConnectOpts, host?: string): Promise<ClientNetSocketProxy>;
224+
createServer(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean }): Promise<ClientNetServerProxy>;
225+
}
226+
211227
export class NetModule implements NodeNet {
212228
public readonly Socket: typeof net.Socket;
213229
public readonly Server: typeof net.Server;
214230

215-
public constructor(private readonly proxy: NetModuleProxy) {
231+
public constructor(private readonly proxy: ClientNetModuleProxy) {
216232
// @ts-ignore this is because Socket is missing things from the Stream
217233
// namespace but I'm unsure how best to provide them (finished,
218234
// finished.__promisify__, pipeline, and some others) or if it even matters.

packages/protocol/src/browser/modules/node-pty.ts

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import * as pty from "node-pty";
2-
import { ClientProxy } from "../../common/proxy";
2+
import { ClientProxy, Module } from "../../common/proxy";
33
import { NodePtyModuleProxy, NodePtyProcessProxy } from "../../node/modules/node-pty";
44

55
// tslint:disable completed-docs
66

7-
export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements pty.IPty {
7+
interface ClientNodePtyProcessProxy extends NodePtyProcessProxy {
8+
proxyId: number | Module;
9+
}
10+
11+
export class NodePtyProcess extends ClientProxy<ClientNodePtyProcessProxy> implements pty.IPty {
812
private _pid = -1;
913
private _process = "";
1014

1115
public constructor(
12-
private readonly moduleProxy: NodePtyModuleProxy,
16+
private readonly moduleProxy: ClientNodePtyModuleProxy,
1317
private readonly file: string,
1418
private readonly args: string[] | string,
1519
private readonly options: pty.IPtyForkOptions,
@@ -18,10 +22,12 @@ export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements
1822
this.on("process", (process) => this._process = process);
1923
}
2024

21-
protected initialize(proxyPromise: Promise<NodePtyProcessProxy>): void {
22-
super.initialize(proxyPromise);
25+
protected initialize(proxyPromise: Promise<ClientNodePtyProcessProxy>): ClientNodePtyProcessProxy {
26+
const proxy = super.initialize(proxyPromise);
2327
this.catch(this.proxy.getPid().then((p) => this._pid = p));
2428
this.catch(this.proxy.getProcess().then((p) => this._process = p));
29+
30+
return proxy;
2531
}
2632

2733
public get pid(): number {
@@ -53,8 +59,13 @@ export class NodePtyProcess extends ClientProxy<NodePtyProcessProxy> implements
5359

5460
type NodePty = typeof pty;
5561

62+
interface ClientNodePtyModuleProxy extends NodePtyModuleProxy {
63+
proxyId: number | Module;
64+
spawn(file: string, args: string[] | string, options: pty.IPtyForkOptions): Promise<ClientNodePtyProcessProxy>;
65+
}
66+
5667
export class NodePtyModule implements NodePty {
57-
public constructor(private readonly proxy: NodePtyModuleProxy) {}
68+
public constructor(private readonly proxy: ClientNodePtyModuleProxy) {}
5869

5970
public spawn = (file: string, args: string[] | string, options: pty.IPtyForkOptions): pty.IPty => {
6071
return new NodePtyProcess(this.proxy, file, args, options);

packages/protocol/src/browser/modules/spdlog.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import * as spdlog from "spdlog";
2-
import { ClientProxy } from "../../common/proxy";
2+
import { ClientProxy, Module } from "../../common/proxy";
33
import { RotatingLoggerProxy, SpdlogModuleProxy } from "../../node/modules/spdlog";
44

55
// tslint:disable completed-docs
66

7-
class RotatingLogger extends ClientProxy<RotatingLoggerProxy> implements spdlog.RotatingLogger {
7+
interface ClientRotatingLoggerProxy extends RotatingLoggerProxy {
8+
proxyId: number | Module;
9+
}
10+
11+
class RotatingLogger extends ClientProxy<ClientRotatingLoggerProxy> implements spdlog.RotatingLogger {
812
public constructor(
9-
private readonly moduleProxy: SpdlogModuleProxy,
13+
private readonly moduleProxy: ClientSpdlogModuleProxy,
1014
private readonly name: string,
1115
private readonly filename: string,
1216
private readonly filesize: number,
@@ -31,10 +35,15 @@ class RotatingLogger extends ClientProxy<RotatingLoggerProxy> implements spdlog.
3135
}
3236
}
3337

38+
interface ClientSpdlogModuleProxy extends SpdlogModuleProxy {
39+
proxyId: number | Module;
40+
createLogger(name: string, filePath: string, fileSize: number, fileCount: number): Promise<ClientRotatingLoggerProxy>;
41+
}
42+
3443
export class SpdlogModule {
3544
public readonly RotatingLogger: typeof spdlog.RotatingLogger;
3645

37-
public constructor(private readonly proxy: SpdlogModuleProxy) {
46+
public constructor(private readonly proxy: ClientSpdlogModuleProxy) {
3847
this.RotatingLogger = class extends RotatingLogger {
3948
public constructor(name: string, filename: string, filesize: number, filecount: number) {
4049
super(proxy, name, filename, filesize, filecount);

packages/protocol/src/browser/modules/stream.ts

+28-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import * as stream from "stream";
22
import { callbackify } from "util";
3-
import { ClientProxy } from "../../common/proxy";
4-
import { DuplexProxy, IReadableProxy, WritableProxy } from "../../node/modules/stream";
3+
import { ClientProxy, Module } from "../../common/proxy";
4+
import { isPromise } from "../../common/util";
5+
import { DuplexProxy, ReadableProxy, WritableProxy } from "../../node/modules/stream";
56

67
// tslint:disable completed-docs
78

8-
export class Writable<T extends WritableProxy = WritableProxy> extends ClientProxy<T> implements stream.Writable {
9+
export interface ClientWritableProxy extends WritableProxy {
10+
proxyId: number | Module;
11+
}
12+
13+
export class Writable<T extends ClientWritableProxy = ClientWritableProxy> extends ClientProxy<T> implements stream.Writable {
914
public get writable(): boolean {
1015
throw new Error("not implemented");
1116
}
@@ -88,7 +93,11 @@ export class Writable<T extends WritableProxy = WritableProxy> extends ClientPro
8893
}
8994
}
9095

91-
export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientProxy<T> implements stream.Readable {
96+
export interface ClientReadableProxy extends ReadableProxy {
97+
proxyId: number | Module;
98+
}
99+
100+
export class Readable<T extends ClientReadableProxy = ClientReadableProxy> extends ClientProxy<T> implements stream.Readable {
92101
public get readable(): boolean {
93102
throw new Error("not implemented");
94103
}
@@ -141,8 +150,16 @@ export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientP
141150
throw new Error("not implemented");
142151
}
143152

144-
public pipe<T>(): T {
145-
throw new Error("not implemented");
153+
public pipe<P extends NodeJS.WritableStream>(destination: P, options?: { end?: boolean }): P {
154+
// tslint:disable-next-line no-any this will be a Writable instance.
155+
const writableProxy = (destination as any as Writable).proxyPromise;
156+
this.catch(
157+
isPromise(writableProxy)
158+
? writableProxy.then((p) => this.proxy.pipe(p, options))
159+
: this.proxy.pipe(writableProxy, options),
160+
);
161+
162+
return destination;
146163
}
147164

148165
// tslint:disable-next-line no-any
@@ -164,7 +181,11 @@ export class Readable<T extends IReadableProxy = IReadableProxy> extends ClientP
164181
}
165182
}
166183

167-
export class Duplex<T extends DuplexProxy = DuplexProxy> extends Writable<T> implements stream.Duplex, stream.Readable {
184+
export interface ClientDuplexProxy extends DuplexProxy {
185+
proxyId: number | Module;
186+
}
187+
188+
export class Duplex<T extends ClientDuplexProxy = ClientDuplexProxy> extends Writable<T> implements stream.Duplex, stream.Readable {
168189
private readonly _readable: Readable;
169190

170191
public constructor(proxyPromise: Promise<T> | T) {

packages/protocol/src/browser/modules/trash.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import * as trash from "trash";
2+
import { Module } from "../../common/proxy";
23
import { TrashModuleProxy } from "../../node/modules/trash";
34

45
// tslint:disable completed-docs
56

7+
interface ClientTrashModuleProxy extends TrashModuleProxy {
8+
proxyId: number | Module;
9+
}
10+
611
export class TrashModule {
7-
public constructor(private readonly proxy: TrashModuleProxy) {}
12+
public constructor(private readonly proxy: ClientTrashModuleProxy) {}
813

914
public trash = (path: string, options?: trash.Options): Promise<void> => {
1015
return this.proxy.trash(path, options);

0 commit comments

Comments
 (0)