Implement fs module (#3)
* Implements the fs module * Add stats object * Add not implemented to createWriteStream * Update mkdtemp to use tmp dir * Unexport Stats * Add client web socket for commands and restructure
This commit is contained in:
221
packages/protocol/src/browser/client.ts
Normal file
221
packages/protocol/src/browser/client.ts
Normal file
@ -0,0 +1,221 @@
|
||||
import { ReadWriteConnection } from "../common/connection";
|
||||
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage } from "../proto";
|
||||
import { Emitter } from "@coder/events";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import { ChildProcess, SpawnOptions, ServerProcess } from "./command";
|
||||
|
||||
/**
|
||||
* Client accepts an arbitrary connection intended to communicate with the Server.
|
||||
*/
|
||||
export class Client {
|
||||
private evalId: number = 0;
|
||||
private evalDoneEmitter: Emitter<EvalDoneMessage> = new Emitter();
|
||||
private evalFailedEmitter: Emitter<EvalFailedMessage> = new Emitter();
|
||||
|
||||
private sessionId: number = 0;
|
||||
private sessions: Map<number, ServerProcess> = new Map();
|
||||
|
||||
/**
|
||||
* @param connection Established connection to the server
|
||||
*/
|
||||
public constructor(
|
||||
private readonly connection: ReadWriteConnection,
|
||||
) {
|
||||
connection.onMessage((data) => {
|
||||
try {
|
||||
this.handleMessage(ServerMessage.deserializeBinary(data));
|
||||
} catch (ex) {
|
||||
logger.error("Failed to handle server message", field("length", data.byteLength), field("exception", ex));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public evaluate<R>(func: () => R | Promise<R>): Promise<R>;
|
||||
public evaluate<R, T1>(func: (a1: T1) => R | Promise<R>, a1: T1): Promise<R>;
|
||||
public evaluate<R, T1, T2>(func: (a1: T1, a2: T2) => R | Promise<R>, a1: T1, a2: T2): Promise<R>;
|
||||
public evaluate<R, T1, T2, T3>(func: (a1: T1, a2: T2, a3: T3) => R | Promise<R>, a1: T1, a2: T2, a3: T3): Promise<R>;
|
||||
public evaluate<R, T1, T2, T3, T4>(func: (a1: T1, a2: T2, a3: T3, a4: T4) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4): Promise<R>;
|
||||
public evaluate<R, T1, T2, T3, T4, T5>(func: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): Promise<R>;
|
||||
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6) => R | Promise<R>, a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): Promise<R>;
|
||||
/**
|
||||
* Evaluates a function on the server.
|
||||
* To pass variables, ensure they are serializable and passed through the included function.
|
||||
* @example
|
||||
* const returned = await this.client.evaluate((value) => {
|
||||
* return value;
|
||||
* }, "hi");
|
||||
* console.log(returned);
|
||||
* // output: "hi"
|
||||
* @param func Function to evaluate
|
||||
* @returns {Promise} Promise rejected or resolved from the evaluated function
|
||||
*/
|
||||
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6) => R | Promise<R>, a1?: T1, a2?: T2, a3?: T3, a4?: T4, a5?: T5, a6?: T6): Promise<R> {
|
||||
const newEval = new NewEvalMessage();
|
||||
const id = this.evalId++;
|
||||
newEval.setId(id);
|
||||
newEval.setArgsList([a1, a2, a3, a4, a5, a6].filter(a => a).map(a => JSON.stringify(a)));
|
||||
newEval.setFunction(func.toString());
|
||||
|
||||
const clientMsg = new ClientMessage();
|
||||
clientMsg.setNewEval(newEval);
|
||||
this.connection.send(clientMsg.serializeBinary());
|
||||
|
||||
let res: (value?: R) => void;
|
||||
let rej: (err?: any) => void;
|
||||
const prom = new Promise<R>((r, e) => {
|
||||
res = r;
|
||||
rej = e;
|
||||
});
|
||||
|
||||
const d1 = this.evalDoneEmitter.event((doneMsg) => {
|
||||
if (doneMsg.getId() === id) {
|
||||
d1.dispose();
|
||||
d2.dispose();
|
||||
|
||||
const resp = doneMsg.getResponse();
|
||||
if (!resp) {
|
||||
res();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const rt = resp.getType();
|
||||
let val: any;
|
||||
switch (rt) {
|
||||
case TypedValue.Type.BOOLEAN:
|
||||
val = resp.getValue() === "true";
|
||||
break;
|
||||
case TypedValue.Type.NUMBER:
|
||||
val = parseInt(resp.getValue(), 10);
|
||||
break;
|
||||
case TypedValue.Type.OBJECT:
|
||||
val = JSON.parse(resp.getValue());
|
||||
break;
|
||||
case TypedValue.Type.STRING:
|
||||
val = resp.getValue();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`unsupported typed value ${rt}`);
|
||||
}
|
||||
|
||||
res(val);
|
||||
}
|
||||
});
|
||||
|
||||
const d2 = this.evalFailedEmitter.event((failedMsg) => {
|
||||
if (failedMsg.getId() === id) {
|
||||
d1.dispose();
|
||||
d2.dispose();
|
||||
|
||||
rej(failedMsg.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return prom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns a process from a command. _Somewhat_ reflects the "child_process" API.
|
||||
* @example
|
||||
* const cp = this.client.spawn("echo", ["test"]);
|
||||
* cp.stdout.on("data", (data) => console.log(data.toString()));
|
||||
* cp.on("exit", (code) => console.log("exited with", code));
|
||||
* @param command
|
||||
* @param args Arguments
|
||||
* @param options Options to execute for the command
|
||||
*/
|
||||
public spawn(command: string, args: string[] = [], options?: SpawnOptions): ChildProcess {
|
||||
return this.doSpawn(command, args, options, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fork a module.
|
||||
* @param modulePath Path of the module
|
||||
* @param args Args to add for the module
|
||||
* @param options Options to execute
|
||||
*/
|
||||
public fork(modulePath: string, args: string[] = [], options?: SpawnOptions): ChildProcess {
|
||||
return this.doSpawn(modulePath, args, options, true);
|
||||
}
|
||||
|
||||
private doSpawn(command: string, args: string[] = [], options?: SpawnOptions, isFork: boolean = false): ChildProcess {
|
||||
const id = this.sessionId++;
|
||||
const newSess = new NewSessionMessage();
|
||||
newSess.setId(id);
|
||||
newSess.setCommand(command);
|
||||
newSess.setArgsList(args);
|
||||
newSess.setIsFork(isFork);
|
||||
if (options) {
|
||||
if (options.cwd) {
|
||||
newSess.setCwd(options.cwd);
|
||||
}
|
||||
if (options.env) {
|
||||
Object.keys(options.env).forEach((envKey) => {
|
||||
newSess.getEnvMap().set(envKey, options.env![envKey]);
|
||||
});
|
||||
}
|
||||
if (options.tty) {
|
||||
const tty = new TTYDimensions();
|
||||
tty.setHeight(options.tty.rows);
|
||||
tty.setWidth(options.tty.columns);
|
||||
newSess.setTtyDimensions(tty);
|
||||
}
|
||||
}
|
||||
const clientMsg = new ClientMessage();
|
||||
clientMsg.setNewSession(newSess);
|
||||
this.connection.send(clientMsg.serializeBinary());
|
||||
|
||||
const serverProc = new ServerProcess(this.connection, id, options ? options.tty !== undefined : false);
|
||||
serverProc.stdin.on("close", () => {
|
||||
console.log("stdin closed");
|
||||
const c = new CloseSessionInputMessage();
|
||||
c.setId(id);
|
||||
const cm = new ClientMessage();
|
||||
cm.setCloseSessionInput(c);
|
||||
this.connection.send(cm.serializeBinary());
|
||||
});
|
||||
this.sessions.set(id, serverProc);
|
||||
return serverProc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a message from the server. All incoming server messages should be
|
||||
* routed through here.
|
||||
*/
|
||||
private handleMessage(message: ServerMessage): void {
|
||||
if (message.hasEvalDone()) {
|
||||
this.evalDoneEmitter.emit(message.getEvalDone()!);
|
||||
} else if (message.hasEvalFailed()) {
|
||||
this.evalFailedEmitter.emit(message.getEvalFailed()!);
|
||||
} else if (message.hasNewSessionFailure()) {
|
||||
const s = this.sessions.get(message.getNewSessionFailure()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.emit("error", new Error(message.getNewSessionFailure()!.getMessage()));
|
||||
this.sessions.delete(message.getNewSessionFailure()!.getId());
|
||||
} else if (message.hasSessionDone()) {
|
||||
const s = this.sessions.get(message.getSessionDone()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.emit("exit", message.getSessionDone()!.getExitStatus());
|
||||
this.sessions.delete(message.getSessionDone()!.getId());
|
||||
} else if (message.hasSessionOutput()) {
|
||||
const output = message.getSessionOutput()!;
|
||||
const s = this.sessions.get(output.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
const data = new TextDecoder().decode(output.getData_asU8());
|
||||
const stream = output.getFd() === SessionOutputMessage.FD.STDOUT ? s.stdout : s.stderr;
|
||||
stream.emit("data", data);
|
||||
} else if (message.hasIdentifySession()) {
|
||||
const s = this.sessions.get(message.getIdentifySession()!.getId());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.pid = message.getIdentifySession()!.getPid();
|
||||
}
|
||||
}
|
||||
}
|
91
packages/protocol/src/browser/command.ts
Normal file
91
packages/protocol/src/browser/command.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import * as events from "events";
|
||||
import * as stream from "stream";
|
||||
import { SendableConnection } from "../common/connection";
|
||||
import { ShutdownSessionMessage, ClientMessage, SessionOutputMessage, WriteToSessionMessage, ResizeSessionTTYMessage, TTYDimensions as ProtoTTYDimensions } from "../proto";
|
||||
|
||||
export interface TTYDimensions {
|
||||
readonly columns: number;
|
||||
readonly rows: number;
|
||||
}
|
||||
|
||||
export interface SpawnOptions {
|
||||
cwd?: string;
|
||||
env?: { readonly [key: string]: string };
|
||||
tty?: TTYDimensions;
|
||||
}
|
||||
|
||||
export interface ChildProcess {
|
||||
readonly stdin: stream.Writable;
|
||||
readonly stdout: stream.Readable;
|
||||
readonly stderr: stream.Readable;
|
||||
|
||||
readonly killed?: boolean;
|
||||
readonly pid: number | undefined;
|
||||
|
||||
kill(signal?: string): void;
|
||||
send(message: string | Uint8Array): void;
|
||||
|
||||
on(event: "error", listener: (err: Error) => void): void;
|
||||
on(event: "exit", listener: (code: number, signal: string) => void): void;
|
||||
|
||||
resize?(dimensions: TTYDimensions): void;
|
||||
}
|
||||
|
||||
export class ServerProcess extends events.EventEmitter implements ChildProcess {
|
||||
public readonly stdin = new stream.Writable();
|
||||
public readonly stdout = new stream.Readable({ read: () => true });
|
||||
public readonly stderr = new stream.Readable({ read: () => true });
|
||||
public pid: number | undefined;
|
||||
|
||||
private _killed: boolean = false;
|
||||
|
||||
public constructor(
|
||||
private readonly connection: SendableConnection,
|
||||
private readonly id: number,
|
||||
private readonly hasTty: boolean = false,
|
||||
) {
|
||||
super();
|
||||
|
||||
if (!this.hasTty) {
|
||||
delete this.resize;
|
||||
}
|
||||
}
|
||||
|
||||
public get killed(): boolean {
|
||||
return this._killed;
|
||||
}
|
||||
|
||||
public kill(signal?: string): void {
|
||||
const kill = new ShutdownSessionMessage();
|
||||
kill.setId(this.id);
|
||||
if (signal) {
|
||||
kill.setSignal(signal);
|
||||
}
|
||||
const client = new ClientMessage();
|
||||
client.setShutdownSession(kill);
|
||||
this.connection.send(client.serializeBinary());
|
||||
|
||||
this._killed = true;
|
||||
}
|
||||
|
||||
public send(message: string | Uint8Array): void {
|
||||
const send = new WriteToSessionMessage();
|
||||
send.setId(this.id);
|
||||
send.setData(typeof message === "string" ? new TextEncoder().encode(message) : message);
|
||||
const client = new ClientMessage();
|
||||
client.setWriteToSession(send);
|
||||
this.connection.send(client.serializeBinary());
|
||||
}
|
||||
|
||||
public resize(dimensions: TTYDimensions) {
|
||||
const resize = new ResizeSessionTTYMessage();
|
||||
resize.setId(this.id);
|
||||
const tty = new ProtoTTYDimensions();
|
||||
tty.setHeight(dimensions.rows);
|
||||
tty.setWidth(dimensions.columns);
|
||||
resize.setTtyDimensions(tty);
|
||||
const client = new ClientMessage();
|
||||
client.setResizeSessionTty(resize);
|
||||
this.connection.send(client.serializeBinary());
|
||||
}
|
||||
}
|
52
packages/protocol/src/browser/modules/child_process.ts
Normal file
52
packages/protocol/src/browser/modules/child_process.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import * as cp from "child_process";
|
||||
import { Client } from "../client";
|
||||
import { useBuffer } from "./util";
|
||||
|
||||
export class CP {
|
||||
|
||||
public constructor(
|
||||
private readonly client: Client,
|
||||
) { }
|
||||
|
||||
public exec(
|
||||
command: string,
|
||||
options?: { encoding?: BufferEncoding | string | "buffer" | null } & cp.ExecOptions | null | ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
callback?: ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
): cp.ChildProcess {
|
||||
const process = this.client.spawn(command);
|
||||
|
||||
let stdout = "";
|
||||
process.stdout.on("data", (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
let stderr = "";
|
||||
process.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
process.on("exit", (exitCode) => {
|
||||
const error = exitCode !== 0 ? new Error(stderr) : null;
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
// @ts-ignore not sure how to make this work.
|
||||
callback(
|
||||
error,
|
||||
useBuffer(options) ? Buffer.from(stdout) : stdout,
|
||||
useBuffer(options) ? Buffer.from(stderr) : stderr,
|
||||
);
|
||||
});
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
public fork(modulePath: string): cp.ChildProcess {
|
||||
return this.client.fork(modulePath);
|
||||
}
|
||||
|
||||
public spawn(command: string, args?: ReadonlyArray<string> | cp.SpawnOptions, _options?: cp.SpawnOptions): cp.ChildProcess {
|
||||
return this.client.spawn(command, args, options);
|
||||
}
|
||||
|
||||
}
|
577
packages/protocol/src/browser/modules/fs.ts
Normal file
577
packages/protocol/src/browser/modules/fs.ts
Normal file
@ -0,0 +1,577 @@
|
||||
import * as fs from "fs";
|
||||
import { Client } from "../client";
|
||||
|
||||
/**
|
||||
* Implements the native fs module
|
||||
* Doesn't use `implements typeof import("fs")` to remove need for __promisify__ impls
|
||||
*/
|
||||
export class FS {
|
||||
|
||||
public constructor(
|
||||
private readonly client: Client,
|
||||
) { }
|
||||
|
||||
public access(path: fs.PathLike, mode: number | undefined, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.access)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public appendFile(file: fs.PathLike | number, data: any, options: { encoding?: string | null, mode?: string | number, flag?: string } | string | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, data, options) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.appendFile)(path, data, options);
|
||||
}, file, data, options).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public chmod(path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.chmod)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public chown(path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, uid, gid) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.chown)(path, uid, gid);
|
||||
}, path, uid, gid).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public close(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((fd) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.close)(fd);
|
||||
}, fd).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public copyFile(src: fs.PathLike, dest: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((src, dest) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.copyFile)(src, dest);
|
||||
}, src, dest).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public createWriteStream(): void {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public exists(path: fs.PathLike, callback: (exists: boolean) => void): void {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.exists)(path);
|
||||
}, path).then((r) => {
|
||||
callback(r);
|
||||
}).catch(() => {
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
|
||||
public fchmod(fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((fd, mode) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.fchmod)(fd, mode);
|
||||
}, fd, mode).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public fchown(fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((fd, uid, gid) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.fchown)(fd, uid, gid);
|
||||
}, fd, uid, gid).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public fdatasync(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((fd) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.fdatasync)(fd);
|
||||
}, fd).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public fstat(fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
|
||||
this.client.evaluate(async (fd) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
const stats = await util.promisify(fs.fstat)(fd);
|
||||
return {
|
||||
...stats,
|
||||
_isBlockDevice: stats.isBlockDevice(),
|
||||
_isCharacterDevice: stats.isCharacterDevice(),
|
||||
_isDirectory: stats.isDirectory(),
|
||||
_isFIFO: stats.isFIFO(),
|
||||
_isFile: stats.isFile(),
|
||||
_isSocket: stats.isSocket(),
|
||||
_isSymbolicLink: stats.isSymbolicLink(),
|
||||
};
|
||||
}, fd).then((stats) => {
|
||||
callback(undefined!, Stats.fromObject(stats));
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public fsync(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((fd) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.fsync)(fd);
|
||||
}, fd).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public ftruncate(fd: number, len: number | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((fd, len) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.ftruncate)(fd, len);
|
||||
}, fd, len).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public futimes(fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((fd, atime, mtime) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.futimes)(fd, atime, mtime);
|
||||
}, fd, atime, mtime).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public lchmod(path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.lchmod)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public lchown(path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, uid, gid) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.lchown)(path, uid, gid);
|
||||
}, path, uid, gid).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public link(existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((existingPath, newPath) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.link)(existingPath, newPath);
|
||||
}, existingPath, newPath).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public lstat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
|
||||
this.client.evaluate(async (path) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
const stats = await util.promisify(fs.lstat)(path);
|
||||
return {
|
||||
...stats,
|
||||
_isBlockDevice: stats.isBlockDevice(),
|
||||
_isCharacterDevice: stats.isCharacterDevice(),
|
||||
_isDirectory: stats.isDirectory(),
|
||||
_isFIFO: stats.isFIFO(),
|
||||
_isFile: stats.isFile(),
|
||||
_isSocket: stats.isSocket(),
|
||||
_isSymbolicLink: stats.isSymbolicLink(),
|
||||
};
|
||||
}, path).then((stats) => {
|
||||
callback(undefined!, Stats.fromObject(stats));
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public mkdir(path: fs.PathLike, mode: number | string | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, mode) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.mkdir)(path, mode);
|
||||
}, path, mode).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public mkdtemp(prefix: string, options: { encoding?: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, folder: string) => void): void {
|
||||
this.client.evaluate((prefix, options) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.mkdtemp)(prefix, options);
|
||||
}, prefix, options).then((folder) => {
|
||||
callback(undefined!, folder);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public open(path: fs.PathLike, flags: string | number, mode: string | number | undefined | null, callback: (err: NodeJS.ErrnoException, fd: number) => void): void {
|
||||
this.client.evaluate((path, flags, mode) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.open)(path, flags, mode);
|
||||
}, path, flags, mode).then((fd) => {
|
||||
callback(undefined!, fd);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public read<TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: TBuffer) => void): void {
|
||||
this.client.evaluate(async (fd, bufferLength, length, position) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
const buffer = new Buffer(length);
|
||||
const resp = await util.promisify(fs.read)(fd, buffer, 0, length, position);
|
||||
|
||||
return {
|
||||
bytesRead: resp.bytesRead,
|
||||
content: buffer.toString("utf8"),
|
||||
};
|
||||
}, fd, buffer.byteLength, length, position).then((resp) => {
|
||||
const newBuf = Buffer.from(resp.content, "utf8");
|
||||
buffer.set(newBuf, offset);
|
||||
callback(undefined!, resp.bytesRead, newBuf as TBuffer);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public readFile(path: fs.PathLike | number, options: string | { encoding?: string | null | undefined; flag?: string | undefined; } | null | undefined, callback: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void {
|
||||
this.client.evaluate(async (path, options) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
const value = await util.promisify(fs.readFile)(path, options);
|
||||
|
||||
return value.toString();
|
||||
}, path, options).then((buffer) => {
|
||||
callback(undefined!, buffer);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public readdir(path: fs.PathLike, options: { encoding: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, files: string[]) => void): void {
|
||||
this.client.evaluate((path, options) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.readdir)(path, options);
|
||||
}, path, options).then((files) => {
|
||||
callback(undefined!, files);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public readlink(path: fs.PathLike, options: { encoding?: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, linkString: string) => void): void {
|
||||
this.client.evaluate((path, options) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.readlink)(path, options);
|
||||
}, path, options).then((linkString) => {
|
||||
callback(undefined!, linkString);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public realpath(path: fs.PathLike, options: { encoding?: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, resolvedPath: string) => void): void {
|
||||
this.client.evaluate((path, options) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.realpath)(path, options);
|
||||
}, path, options).then((resolvedPath) => {
|
||||
callback(undefined!, resolvedPath);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public rename(oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((oldPath, newPath) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.rename)(oldPath, newPath);
|
||||
}, oldPath, newPath).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public rmdir(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.rmdir)(path);
|
||||
}, path).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public stat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
|
||||
this.client.evaluate(async (path) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
const stats = await util.promisify(fs.stat)(path);
|
||||
return {
|
||||
...stats,
|
||||
_isBlockDevice: stats.isBlockDevice(),
|
||||
_isCharacterDevice: stats.isCharacterDevice(),
|
||||
_isDirectory: stats.isDirectory(),
|
||||
_isFIFO: stats.isFIFO(),
|
||||
_isFile: stats.isFile(),
|
||||
_isSocket: stats.isSocket(),
|
||||
_isSymbolicLink: stats.isSymbolicLink(),
|
||||
};
|
||||
}, path).then((stats) => {
|
||||
callback(undefined!, Stats.fromObject(stats));
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public symlink(target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((target, path, type) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.symlink)(target, path, type);
|
||||
}, target, path, type).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public truncate(path: fs.PathLike, len: number | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, len) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.truncate)(path, len);
|
||||
}, path, len).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public unlink(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.unlink)(path);
|
||||
}, path).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public utimes(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, atime, mtime) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.utimes)(path, atime, mtime);
|
||||
}, path, atime, mtime).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
|
||||
public write<TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number | undefined, length: number | undefined, position: number | undefined, callback: (err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void): void {
|
||||
this.client.evaluate(async (fd, buffer, offset, length, position) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
const resp = await util.promisify(fs.write)(fd, Buffer.from(buffer, "utf8"), offset, length, position);
|
||||
|
||||
return {
|
||||
bytesWritten: resp.bytesWritten,
|
||||
content: resp.buffer.toString("utf8"),
|
||||
}
|
||||
}, fd, buffer.toString(), offset, length, position).then((r) => {
|
||||
callback(undefined!, r.bytesWritten, Buffer.from(r.content, "utf8") as TBuffer);
|
||||
}).catch((ex) => {
|
||||
callback(ex, undefined!, undefined!);
|
||||
});
|
||||
}
|
||||
|
||||
public writeFile(path: fs.PathLike | number, data: any, options: { encoding?: string | null; mode?: number | string; flag?: string; } | string | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
this.client.evaluate((path, data, options) => {
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const util = require("util") as typeof import("util");
|
||||
return util.promisify(fs.writeFile)(path, data, options);
|
||||
}, path, data, options).then(() => {
|
||||
callback(undefined!);
|
||||
}).catch((ex) => {
|
||||
callback(ex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Stats implements fs.Stats {
|
||||
|
||||
public static fromObject(object: any): Stats {
|
||||
return new Stats(object);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
public readonly dev: number;
|
||||
// @ts-ignore
|
||||
public readonly ino: number;
|
||||
// @ts-ignore
|
||||
public readonly mode: number;
|
||||
// @ts-ignore
|
||||
public readonly nlink: number;
|
||||
// @ts-ignore
|
||||
public readonly uid: number;
|
||||
// @ts-ignore
|
||||
public readonly gid: number;
|
||||
// @ts-ignore
|
||||
public readonly rdev: number;
|
||||
// @ts-ignore
|
||||
public readonly size: number;
|
||||
// @ts-ignore
|
||||
public readonly blksize: number;
|
||||
// @ts-ignore
|
||||
public readonly blocks: number;
|
||||
// @ts-ignore
|
||||
public readonly atimeMs: number;
|
||||
// @ts-ignore
|
||||
public readonly mtimeMs: number;
|
||||
// @ts-ignore
|
||||
public readonly ctimeMs: number;
|
||||
// @ts-ignore
|
||||
public readonly birthtimeMs: number;
|
||||
// @ts-ignore
|
||||
public readonly atime: Date;
|
||||
// @ts-ignore
|
||||
public readonly mtime: Date;
|
||||
// @ts-ignore
|
||||
public readonly ctime: Date;
|
||||
// @ts-ignore
|
||||
public readonly birthtime: Date;
|
||||
// @ts-ignore
|
||||
private readonly _isFile: boolean;
|
||||
// @ts-ignore
|
||||
private readonly _isDirectory: boolean;
|
||||
// @ts-ignore
|
||||
private readonly _isBlockDevice: boolean;
|
||||
// @ts-ignore
|
||||
private readonly _isCharacterDevice: boolean;
|
||||
// @ts-ignore
|
||||
private readonly _isSymbolicLink: boolean;
|
||||
// @ts-ignore
|
||||
private readonly _isFIFO: boolean;
|
||||
// @ts-ignore
|
||||
private readonly _isSocket: boolean;
|
||||
|
||||
private constructor(stats: object) {
|
||||
Object.assign(this, stats);
|
||||
}
|
||||
|
||||
public isFile(): boolean {
|
||||
return this._isFile;
|
||||
}
|
||||
|
||||
public isDirectory(): boolean {
|
||||
return this._isDirectory;
|
||||
}
|
||||
|
||||
public isBlockDevice(): boolean {
|
||||
return this._isBlockDevice;
|
||||
}
|
||||
|
||||
public isCharacterDevice(): boolean {
|
||||
return this._isCharacterDevice;
|
||||
}
|
||||
|
||||
public isSymbolicLink(): boolean {
|
||||
return this._isSymbolicLink;
|
||||
}
|
||||
|
||||
public isFIFO(): boolean {
|
||||
return this._isFIFO;
|
||||
}
|
||||
|
||||
public isSocket(): boolean {
|
||||
return this._isSocket;
|
||||
}
|
||||
|
||||
public toObject(): object {
|
||||
return JSON.parse(JSON.stringify(this));
|
||||
}
|
||||
|
||||
}
|
72
packages/protocol/src/browser/modules/net.ts
Normal file
72
packages/protocol/src/browser/modules/net.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import * as net from "net";
|
||||
|
||||
/**
|
||||
* Implementation of Socket for the browser.
|
||||
*/
|
||||
class Socket extends net.Socket {
|
||||
|
||||
public connect(): this {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Server for the browser.
|
||||
*/
|
||||
class Server extends net.Server {
|
||||
|
||||
public listen(
|
||||
_port?: number | any | net.ListenOptions, // tslint:disable-line no-any so we can match the Node API.
|
||||
_hostname?: string | number | Function,
|
||||
_backlog?: number | Function,
|
||||
_listeningListener?: Function,
|
||||
): this {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type NodeNet = typeof net;
|
||||
|
||||
/**
|
||||
* Implementation of net for the browser.
|
||||
*/
|
||||
export class Net implements NodeNet {
|
||||
|
||||
public get Socket(): typeof net.Socket {
|
||||
return Socket;
|
||||
}
|
||||
|
||||
public get Server(): typeof net.Server {
|
||||
return Server;
|
||||
}
|
||||
|
||||
public connect(): net.Socket {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public createConnection(): net.Socket {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public isIP(_input: string): number {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public isIPv4(_input: string): boolean {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public isIPv6(_input: string): boolean {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
public createServer(
|
||||
_options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
|
||||
_connectionListener?: (socket: net.Socket) => void,
|
||||
): Server {
|
||||
return new Server();
|
||||
}
|
||||
|
||||
}
|
8
packages/protocol/src/browser/modules/util.ts
Normal file
8
packages/protocol/src/browser/modules/util.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Return true if the options specify to use a Buffer instead of string.
|
||||
*/
|
||||
export const useBuffer = (options: { encoding?: string | null } | string | undefined | null | Function): boolean => {
|
||||
return options === "buffer"
|
||||
|| (!!options && typeof options !== "string" && typeof options !== "function"
|
||||
&& (options.encoding === "buffer" || options.encoding === null));
|
||||
};
|
Reference in New Issue
Block a user