Archived
1
0

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:
Kyle Carberry
2019-01-14 14:58:34 -06:00
parent da27bc0f04
commit a328204d80
68 changed files with 10467 additions and 2274 deletions

View 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();
}
}
}

View 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());
}
}

View 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);
}
}

View 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));
}
}

View 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();
}
}

View 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));
};