Bit of cleanup, some test fixes, moving some funcs
This commit is contained in:
parent
dc1a16ee0b
commit
5ea1d8b2aa
@ -13,8 +13,10 @@ import { clipboard } from "./clipboard";
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This is required to make the fill load in Node without erroring.
|
||||||
if (typeof document === "undefined") {
|
if (typeof document === "undefined") {
|
||||||
(<any>global).document = {} as any;
|
// tslint:disable-next-line no-any
|
||||||
|
(global as any).document = {} as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldCreateElement: <K extends keyof HTMLElementTagNameMap>(
|
const oldCreateElement: <K extends keyof HTMLElementTagNameMap>(
|
||||||
@ -52,7 +54,7 @@ const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HT
|
|||||||
if (view.contentDocument) {
|
if (view.contentDocument) {
|
||||||
view.contentDocument.body.id = frameID;
|
view.contentDocument.body.id = frameID;
|
||||||
view.contentDocument.body.parentElement!.style.overflow = "hidden";
|
view.contentDocument.body.parentElement!.style.overflow = "hidden";
|
||||||
const script = document.createElement("script");
|
const script = createElement("script");
|
||||||
script.src = url;
|
script.src = url;
|
||||||
view.contentDocument.head.appendChild(script);
|
view.contentDocument.head.appendChild(script);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ class Net implements NodeNet {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
public get Socket(): typeof net.Socket {
|
public get Socket(): typeof net.Socket {
|
||||||
// @ts-ignore
|
|
||||||
return this.client.Socket;
|
return this.client.Socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ export class Upload {
|
|||||||
|
|
||||||
await rm();
|
await rm();
|
||||||
|
|
||||||
reader.addEventListener("load", async () => {
|
const load = async (): Promise<void> => {
|
||||||
const buffer = new Uint8Array(reader.result as ArrayBuffer);
|
const buffer = new Uint8Array(reader.result as ArrayBuffer);
|
||||||
let bufferOffset = 0;
|
let bufferOffset = 0;
|
||||||
|
|
||||||
@ -259,7 +259,9 @@ export class Upload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
seek();
|
seek();
|
||||||
});
|
};
|
||||||
|
|
||||||
|
reader.addEventListener("load", load);
|
||||||
|
|
||||||
seek();
|
seek();
|
||||||
});
|
});
|
||||||
|
@ -56,57 +56,6 @@ export const field = <T>(name: string, value: T): Field<T> => {
|
|||||||
return new Field(name, value);
|
return new Field(name, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Hashes a string.
|
|
||||||
*/
|
|
||||||
const djb2 = (str: string): number => {
|
|
||||||
let hash = 5381;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert rgb to hex.
|
|
||||||
*/
|
|
||||||
const rgbToHex = (r: number, g: number, b: number): string => {
|
|
||||||
const integer = ((Math.round(r) & 0xFF) << 16)
|
|
||||||
+ ((Math.round(g) & 0xFF) << 8)
|
|
||||||
+ (Math.round(b) & 0xFF);
|
|
||||||
|
|
||||||
const str = integer.toString(16);
|
|
||||||
|
|
||||||
return "#" + "000000".substring(str.length) + str;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert fully-formed hex to rgb.
|
|
||||||
*/
|
|
||||||
const hexToRgb = (hex: string): [number, number, number] => {
|
|
||||||
const integer = parseInt(hex.substr(1), 16);
|
|
||||||
|
|
||||||
return [
|
|
||||||
(integer >> 16) & 0xFF,
|
|
||||||
(integer >> 8) & 0xFF,
|
|
||||||
integer & 0xFF,
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a deterministic color from a string using hashing.
|
|
||||||
*/
|
|
||||||
const hashStringToColor = (str: string): string => {
|
|
||||||
const hash = djb2(str);
|
|
||||||
|
|
||||||
return rgbToHex(
|
|
||||||
(hash & 0xFF0000) >> 16,
|
|
||||||
(hash & 0x00FF00) >> 8,
|
|
||||||
hash & 0x0000FF,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This formats & builds text for logging.
|
* This formats & builds text for logging.
|
||||||
* It should only be used to build one log item at a time since it stores the
|
* It should only be used to build one log item at a time since it stores the
|
||||||
@ -207,7 +156,7 @@ export class BrowserFormatter extends Formatter {
|
|||||||
*/
|
*/
|
||||||
export class ServerFormatter extends Formatter {
|
export class ServerFormatter extends Formatter {
|
||||||
public tag(name: string, color: string): void {
|
public tag(name: string, color: string): void {
|
||||||
const [r, g, b] = hexToRgb(color);
|
const [r, g, b] = this.hexToRgb(color);
|
||||||
while (name.length < 5) {
|
while (name.length < 5) {
|
||||||
name += " ";
|
name += " ";
|
||||||
}
|
}
|
||||||
@ -220,7 +169,7 @@ export class ServerFormatter extends Formatter {
|
|||||||
this.format += "\u001B[1m";
|
this.format += "\u001B[1m";
|
||||||
}
|
}
|
||||||
if (color) {
|
if (color) {
|
||||||
const [r, g, b] = hexToRgb(color);
|
const [r, g, b] = this.hexToRgb(color);
|
||||||
this.format += `\u001B[38;2;${r};${g};${b}m`;
|
this.format += `\u001B[38;2;${r};${g};${b}m`;
|
||||||
}
|
}
|
||||||
this.format += this.getType(arg);
|
this.format += this.getType(arg);
|
||||||
@ -241,6 +190,19 @@ export class ServerFormatter extends Formatter {
|
|||||||
this.args.push(JSON.stringify(obj));
|
this.args.push(JSON.stringify(obj));
|
||||||
console.log(...this.flush()); // tslint:disable-line no-console
|
console.log(...this.flush()); // tslint:disable-line no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert fully-formed hex to rgb.
|
||||||
|
*/
|
||||||
|
private hexToRgb(hex: string): [number, number, number] {
|
||||||
|
const integer = parseInt(hex.substr(1), 16);
|
||||||
|
|
||||||
|
return [
|
||||||
|
(integer >> 16) & 0xFF,
|
||||||
|
(integer >> 8) & 0xFF,
|
||||||
|
integer & 0xFF,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -258,7 +220,7 @@ export class Logger {
|
|||||||
private readonly defaultFields?: FieldArray,
|
private readonly defaultFields?: FieldArray,
|
||||||
) {
|
) {
|
||||||
if (name) {
|
if (name) {
|
||||||
this.nameColor = hashStringToColor(name);
|
this.nameColor = this.hashStringToColor(name);
|
||||||
}
|
}
|
||||||
const envLevel = typeof global !== "undefined" && typeof global.process !== "undefined" ? global.process.env.LOG_LEVEL : process.env.LOG_LEVEL;
|
const envLevel = typeof global !== "undefined" && typeof global.process !== "undefined" ? global.process.env.LOG_LEVEL : process.env.LOG_LEVEL;
|
||||||
if (envLevel) {
|
if (envLevel) {
|
||||||
@ -401,7 +363,7 @@ export class Logger {
|
|||||||
const green = expPer < 1 ? max : min;
|
const green = expPer < 1 ? max : min;
|
||||||
const red = expPer >= 1 ? max : min;
|
const red = expPer >= 1 ? max : min;
|
||||||
this._formatter.push(` ${time.identifier}=`, "#3794ff");
|
this._formatter.push(` ${time.identifier}=`, "#3794ff");
|
||||||
this._formatter.push(`${diff}ms`, rgbToHex(red > 0 ? red : 0, green > 0 ? green : 0, 0));
|
this._formatter.push(`${diff}ms`, this.rgbToHex(red > 0 ? red : 0, green > 0 ? green : 0, 0));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,6 +375,44 @@ export class Logger {
|
|||||||
}
|
}
|
||||||
// tslint:enable no-console
|
// tslint:enable no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashes a string.
|
||||||
|
*/
|
||||||
|
private djb2(str: string): number {
|
||||||
|
let hash = 5381;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert rgb to hex.
|
||||||
|
*/
|
||||||
|
private rgbToHex(r: number, g: number, b: number): string {
|
||||||
|
const integer = ((Math.round(r) & 0xFF) << 16)
|
||||||
|
+ ((Math.round(g) & 0xFF) << 8)
|
||||||
|
+ (Math.round(b) & 0xFF);
|
||||||
|
|
||||||
|
const str = integer.toString(16);
|
||||||
|
|
||||||
|
return "#" + "000000".substring(str.length) + str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a deterministic color from a string using hashing.
|
||||||
|
*/
|
||||||
|
private hashStringToColor(str: string): string {
|
||||||
|
const hash = this.djb2(str);
|
||||||
|
|
||||||
|
return this.rgbToHex(
|
||||||
|
(hash & 0xFF0000) >> 16,
|
||||||
|
(hash & 0x00FF00) >> 8,
|
||||||
|
hash & 0x0000FF,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const logger = new Logger(
|
export const logger = new Logger(
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { ReadWriteConnection, InitData, OperatingSystem, ISharedProcessData } from "../common/connection";
|
import { ReadWriteConnection, InitData, OperatingSystem, ISharedProcessData } from "../common/connection";
|
||||||
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage, WorkingInitMessage, EvalEventMessage } from "../proto";
|
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage, WorkingInitMessage, EvalEventMessage } from "../proto";
|
||||||
import { Emitter, Event } from "@coder/events";
|
import { Emitter } from "@coder/events";
|
||||||
import { logger, field } from "@coder/logger";
|
import { logger, field } from "@coder/logger";
|
||||||
import { ChildProcess, SpawnOptions, ForkOptions, ServerProcess, ServerSocket, Socket, ServerListener, Server, ActiveEval } from "./command";
|
import { ChildProcess, SpawnOptions, ForkOptions, ServerProcess, ServerSocket, Socket, ServerListener, Server, ActiveEval } from "./command";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
import { Socket as NetSocket } from "net";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client accepts an arbitrary connection intended to communicate with the Server.
|
* Client accepts an arbitrary connection intended to communicate with the Server.
|
||||||
*/
|
*/
|
||||||
export class Client {
|
export class Client {
|
||||||
public readonly Socket: typeof ServerSocket;
|
public readonly Socket: typeof NetSocket;
|
||||||
|
|
||||||
private evalId = 0;
|
private evalId = 0;
|
||||||
private readonly evalDoneEmitter = new Emitter<EvalDoneMessage>();
|
private readonly evalDoneEmitter = new Emitter<EvalDoneMessage>();
|
||||||
@ -47,12 +48,11 @@ export class Client {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const that = this;
|
const that = this;
|
||||||
|
// @ts-ignore NOTE: this doesn't fully implement net.Socket.
|
||||||
this.Socket = class extends ServerSocket {
|
this.Socket = class extends ServerSocket {
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super(that.connection, that.connectionId++, that.registerConnection);
|
super(that.connection, that.connectionId++, that.registerConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.initDataPromise = new Promise((resolve): void => {
|
this.initDataPromise = new Promise((resolve): void => {
|
||||||
@ -151,19 +151,20 @@ export class Client {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const d1 = this.evalDoneEmitter.event((doneMsg) => {
|
const d1 = this.evalDoneEmitter.event((doneMsg) => {
|
||||||
if (doneMsg.getId() === id) {
|
if (doneMsg.getId() !== id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
d1.dispose();
|
d1.dispose();
|
||||||
d2.dispose();
|
d2.dispose();
|
||||||
|
|
||||||
const resp = doneMsg.getResponse();
|
const resp = doneMsg.getResponse();
|
||||||
if (!resp) {
|
if (!resp) {
|
||||||
res();
|
return res();
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rt = resp.getType();
|
const rt = resp.getType();
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line no-any
|
||||||
let val: any;
|
let val: any;
|
||||||
switch (rt) {
|
switch (rt) {
|
||||||
case TypedValue.Type.BOOLEAN:
|
case TypedValue.Type.BOOLEAN:
|
||||||
@ -183,7 +184,6 @@ export class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res(val);
|
res(val);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const d2 = this.evalFailedEmitter.event((failedMsg) => {
|
const d2 = this.evalFailedEmitter.event((failedMsg) => {
|
||||||
|
@ -4,7 +4,7 @@ import * as os from "os";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { TextEncoder, TextDecoder } from "text-encoding";
|
import { TextEncoder, TextDecoder } from "text-encoding";
|
||||||
import { createClient } from "./helpers";
|
import { createClient } from "./helpers";
|
||||||
import { Net } from "../src/browser/modules/net";
|
import { ChildProcess } from "../src/browser/command";
|
||||||
|
|
||||||
(global as any).TextDecoder = TextDecoder; // tslint:disable-line no-any
|
(global as any).TextDecoder = TextDecoder; // tslint:disable-line no-any
|
||||||
(global as any).TextEncoder = TextEncoder; // tslint:disable-line no-any
|
(global as any).TextEncoder = TextEncoder; // tslint:disable-line no-any
|
||||||
@ -13,6 +13,7 @@ describe("spawn", () => {
|
|||||||
const client = createClient({
|
const client = createClient({
|
||||||
dataDirectory: "",
|
dataDirectory: "",
|
||||||
workingDirectory: "",
|
workingDirectory: "",
|
||||||
|
builtInExtensionsDirectory: "",
|
||||||
forkProvider: (msg): cp.ChildProcess => {
|
forkProvider: (msg): cp.ChildProcess => {
|
||||||
return cp.spawn(msg.getCommand(), msg.getArgsList(), {
|
return cp.spawn(msg.getCommand(), msg.getArgsList(), {
|
||||||
stdio: [null, null, null, "ipc"],
|
stdio: [null, null, null, "ipc"],
|
||||||
@ -20,6 +21,37 @@ describe("spawn", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function that when called returns a promise that resolves with
|
||||||
|
* the next chunk of data from the process.
|
||||||
|
*/
|
||||||
|
const promisifyData = (proc: ChildProcess): (() => Promise<string>) => {
|
||||||
|
// Use a persistent callback instead of creating it in the promise since
|
||||||
|
// otherwise we could lose data that comes in while no promise is listening.
|
||||||
|
let onData: (() => void) | undefined;
|
||||||
|
let buffer: string | undefined;
|
||||||
|
proc.stdout.on("data", (data) => {
|
||||||
|
// Remove everything that isn't a letter, number, or $ to avoid issues
|
||||||
|
// with ANSI escape codes printing inside the test output.
|
||||||
|
buffer = (buffer || "") + data.toString().replace(/[^a-zA-Z0-9$]/g, "");
|
||||||
|
if (onData) {
|
||||||
|
onData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (): Promise<string> => new Promise((resolve): void => {
|
||||||
|
onData = (): void => {
|
||||||
|
if (typeof buffer !== "undefined") {
|
||||||
|
const data = buffer;
|
||||||
|
buffer = undefined;
|
||||||
|
onData = undefined;
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onData();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
it("should execute command and return output", (done) => {
|
it("should execute command and return output", (done) => {
|
||||||
const proc = client.spawn("echo", ["test"]);
|
const proc = client.spawn("echo", ["test"]);
|
||||||
proc.stdout.on("data", (data) => {
|
proc.stdout.on("data", (data) => {
|
||||||
@ -30,25 +62,30 @@ describe("spawn", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create shell", (done) => {
|
it("should create shell", async () => {
|
||||||
const proc = client.spawn("/bin/bash", [], {
|
// Setting the config file to something that shouldn't exist so the test
|
||||||
|
// isn't affected by custom configuration.
|
||||||
|
const proc = client.spawn("/bin/bash", ["--rcfile", "/tmp/test/nope/should/not/exist"], {
|
||||||
tty: {
|
tty: {
|
||||||
columns: 100,
|
columns: 100,
|
||||||
rows: 10,
|
rows: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let first = true;
|
|
||||||
proc.stdout.on("data", (data) => {
|
|
||||||
if (first) {
|
|
||||||
// First piece of data is a welcome msg. Second is the prompt
|
|
||||||
first = false;
|
|
||||||
|
|
||||||
return;
|
const getData = promisifyData(proc);
|
||||||
}
|
|
||||||
expect(data.toString().endsWith("$ ")).toBeTruthy();
|
// First it outputs @hostname:cwd
|
||||||
|
expect((await getData()).length).toBeGreaterThan(1);
|
||||||
|
|
||||||
|
// Then it seems to overwrite that with a shorter prompt in the format of
|
||||||
|
// [hostname@user]$
|
||||||
|
expect((await getData())).toContain("$");
|
||||||
|
|
||||||
proc.kill();
|
proc.kill();
|
||||||
|
|
||||||
|
await new Promise((resolve): void => {
|
||||||
|
proc.on("exit", resolve);
|
||||||
});
|
});
|
||||||
proc.on("exit", () => done());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should cat", (done) => {
|
it("should cat", (done) => {
|
||||||
@ -74,68 +111,46 @@ describe("spawn", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should resize", (done) => {
|
it("should resize", async () => {
|
||||||
// Requires the `tput lines` cmd to be available
|
// Requires the `tput lines` cmd to be available.
|
||||||
|
// Setting the config file to something that shouldn't exist so the test
|
||||||
const proc = client.spawn("/bin/bash", [], {
|
// isn't affected by custom configuration.
|
||||||
|
const proc = client.spawn("/bin/bash", ["--rcfile", "/tmp/test/nope/should/not/exist"], {
|
||||||
tty: {
|
tty: {
|
||||||
columns: 10,
|
columns: 10,
|
||||||
rows: 10,
|
rows: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let output: number = 0; // Number of outputs parsed
|
|
||||||
proc.stdout.on("data", (data) => {
|
|
||||||
output++;
|
|
||||||
|
|
||||||
if (output === 1) {
|
const getData = promisifyData(proc);
|
||||||
// First is welcome msg
|
|
||||||
return;
|
// We've already tested these first two bits of output; see shell test.
|
||||||
}
|
await getData();
|
||||||
|
await getData();
|
||||||
|
|
||||||
if (output === 2) {
|
|
||||||
proc.send("tput lines\n");
|
proc.send("tput lines\n");
|
||||||
|
expect(await getData()).toContain("tput");
|
||||||
|
|
||||||
return;
|
expect((await getData()).trim()).toContain("10");
|
||||||
}
|
|
||||||
|
|
||||||
if (output === 3) {
|
|
||||||
// Echo of tput lines
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output === 4) {
|
|
||||||
expect(data.toString().trim()).toEqual("10");
|
|
||||||
proc.resize!({
|
proc.resize!({
|
||||||
columns: 10,
|
columns: 10,
|
||||||
rows: 50,
|
rows: 50,
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
// The prompt again.
|
||||||
}
|
await getData();
|
||||||
|
await getData();
|
||||||
|
|
||||||
if (output === 5) {
|
|
||||||
// Primpt
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output === 6) {
|
|
||||||
proc.send("tput lines\n");
|
proc.send("tput lines\n");
|
||||||
|
expect(await getData()).toContain("tput");
|
||||||
|
|
||||||
return;
|
expect((await getData())).toContain("50");
|
||||||
}
|
|
||||||
|
|
||||||
if (output === 7) {
|
|
||||||
// Echo of tput lines
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output === 8) {
|
|
||||||
expect(data.toString().trim()).toEqual("50");
|
|
||||||
proc.kill();
|
proc.kill();
|
||||||
expect(proc.killed).toBeTruthy();
|
expect(proc.killed).toBeTruthy();
|
||||||
}
|
await new Promise((resolve): void => {
|
||||||
|
proc.on("exit", resolve);
|
||||||
});
|
});
|
||||||
proc.on("exit", () => done());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fork and echo messages", (done) => {
|
it("should fork and echo messages", (done) => {
|
||||||
@ -176,7 +191,7 @@ describe("createConnection", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await new Promise((resolve): void => {
|
await new Promise((resolve): void => {
|
||||||
const socket = new (new Net(client)).Socket();
|
const socket = new client.Socket();
|
||||||
socket.connect(tmpPath, () => {
|
socket.connect(tmpPath, () => {
|
||||||
socket.end();
|
socket.end();
|
||||||
socket.addListener("close", () => {
|
socket.addListener("close", () => {
|
||||||
|
Reference in New Issue
Block a user