Archived
1
0

Getting the client to run (#12)

* Clean up workbench and integrate initialization data

* Uncomment Electron fill

* Run server & client together

* Clean up Electron fill & patch

* Bind fs methods

This makes them usable with the promise form:
`promisify(access)(...)`.

* Add space between tag and title to browser logger

* Add typescript dep to server and default __dirname for path

* Serve web files from server

* Adjust some dev options

* Rework workbench a bit to use a class and catch unexpected errors

* No mkdirs for now, fix util fill, use bash with exec

* More fills, make general client abstract

* More fills

* Fix cp.exec

* Fix require calls in fs fill being aliased

* Create data and storage dir

* Implement fs.watch

Using exec for now.

* Implement storage database fill

* Fix os export and homedir

* Add comment to use navigator.sendBeacon

* Fix fs callbacks (some args are optional)

* Make sure data directory exists when passing it back

* Update patch

* Target es5

* More fills

* Add APIs required for bootstrap-fork to function (#15)

* Add bootstrap-fork execution

* Add createConnection

* Bundle bootstrap-fork into cli

* Remove .node directory created from spdlog

* Fix npm start

* Remove unnecessary comment

* Add webpack-hot-middleware if CLI env is not set

* Add restarting to shared process

* Fix starting with yarn
This commit is contained in:
Asher
2019-01-18 15:46:40 -06:00
committed by Kyle Carberry
parent 05899b5edf
commit 72bf4547d4
80 changed files with 5183 additions and 9697 deletions

View File

@ -1,5 +1,5 @@
import { Emitter } from "@coder/events";
import { logger, field } from "@coder/logger";
import { field, logger } from "@coder/logger";
import { Client, ReadWriteConnection } from "@coder/protocol";
import { retry } from "../retry";
@ -10,27 +10,20 @@ import { retry } from "../retry";
class Connection implements ReadWriteConnection {
private activeSocket: WebSocket | undefined;
private readonly messageEmitter: Emitter<Uint8Array>;
private readonly closeEmitter: Emitter<void>;
private readonly upEmitter: Emitter<void>;
private readonly downEmitter: Emitter<void>;
private readonly messageBuffer: Uint8Array[];
private readonly messageEmitter: Emitter<Uint8Array> = new Emitter();
private readonly closeEmitter: Emitter<void> = new Emitter();
private readonly upEmitter: Emitter<void> = new Emitter();
private readonly downEmitter: Emitter<void> = new Emitter();
private readonly messageBuffer: Uint8Array[] = [];
private socketTimeoutDelay = 60 * 1000;
private retryName = "Web socket";
private isUp: boolean | undefined;
private closed: boolean | undefined;
private isUp: boolean = false;
private closed: boolean = false;
public constructor() {
this.messageEmitter = new Emitter();
this.closeEmitter = new Emitter();
this.upEmitter = new Emitter();
this.downEmitter = new Emitter();
this.messageBuffer = [];
retry.register(this.retryName, () => this.connect());
this.connect().catch(() => {
retry.block(this.retryName);
retry.run(this.retryName);
});
retry.block(this.retryName);
retry.run(this.retryName);
}
/**
@ -72,7 +65,7 @@ class Connection implements ReadWriteConnection {
this.closeEmitter.emit();
}
/**
/**
* Connect to the server.
*/
private async connect(): Promise<void> {
@ -116,7 +109,7 @@ class Connection implements ReadWriteConnection {
private async openSocket(): Promise<WebSocket> {
this.dispose();
const socket = new WebSocket(
`${location.protocol === "https" ? "wss" : "ws"}://${location.host}/websocket`,
`${location.protocol === "https" ? "wss" : "ws"}://${location.host}`,
);
socket.binaryType = "arraybuffer";
this.activeSocket = socket;
@ -153,7 +146,5 @@ class Connection implements ReadWriteConnection {
}
/**
* A client for proxying Node APIs based on web sockets.
*/
// Global instance so all fills can use the same client.
export const client = new Client(new Connection());

View File

@ -0,0 +1,132 @@
import { IDisposable } from "@coder/disposable";
import { Emitter } from "@coder/events";
/**
* Native clipboard.
*/
export class Clipboard {
private readonly enableEmitter: Emitter<boolean> = new Emitter();
private _isEnabled: boolean = false;
/**
* Ask for permission to use the clipboard.
*/
public initialize(): void {
// tslint:disable no-any
const navigatorClip = (navigator as any).clipboard;
const navigatorPerms = (navigator as any).permissions;
// tslint:enable no-any
if (navigatorClip && navigatorPerms) {
navigatorPerms.query({
name: "clipboard-read",
}).then((permissionStatus: {
onchange: () => void,
state: "denied" | "granted" | "prompt",
}) => {
const updateStatus = (): void => {
this._isEnabled = permissionStatus.state !== "denied";
this.enableEmitter.emit(this.isEnabled);
};
updateStatus();
permissionStatus.onchange = (): void => {
updateStatus();
};
});
}
}
/**
* Return true if the native clipboard is supported.
*/
public get isSupported(): boolean {
// tslint:disable no-any
return typeof navigator !== "undefined"
&& typeof (navigator as any).clipboard !== "undefined"
&& typeof (navigator as any).clipboard.readText !== "undefined";
// tslint:enable no-any
}
/**
* Register a function to be called when the native clipboard is
* enabled/disabled.
*/
public onPermissionChange(cb: (enabled: boolean) => void): IDisposable {
return this.enableEmitter.event(cb);
}
/**
* Read text from the clipboard.
*/
public readText(): Promise<string> {
return this.instance ? this.instance.readText() : Promise.resolve("");
}
/**
* Write text to the clipboard.
*/
public writeText(value: string): Promise<void> {
return this.instance
? this.instance.writeText(value)
: this.writeTextFallback(value);
}
/**
* Return true if the clipboard is currently enabled.
*/
public get isEnabled(): boolean {
return !!this._isEnabled;
}
/**
* Return clipboard instance if there is one.
*/
private get instance(): ({
readText(): Promise<string>;
writeText(value: string): Promise<void>;
}) | undefined {
// tslint:disable-next-line no-any
return this.isSupported ? (navigator as any).clipboard : undefined;
}
/**
* Fallback for writing text to the clipboard.
* Taken from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
*/
private writeTextFallback(value: string): Promise<void> {
// Note the current focus and selection.
const active = document.activeElement as HTMLElement;
const selection = document.getSelection();
const selected = selection && selection.rangeCount > 0
? selection.getRangeAt(0)
: false;
// Insert a hidden textarea to put the text to copy in.
const el = document.createElement("textarea");
el.value = value;
el.setAttribute("readonly", "");
el.style.position = "absolute";
el.style.left = "-9999px";
document.body.appendChild(el);
// Select the textarea and execute a copy (this will only work as part of a
// user interaction).
el.select();
document.execCommand("copy");
// Remove the textarea and put focus and selection back to where it was
// previously.
document.body.removeChild(el);
active.focus();
if (selected && selection) {
selection.removeAllRanges();
selection.addRange(selected);
}
return Promise.resolve();
}
}
// Global clipboard instance since it's used in the Electron fill.
export const clipboard = new Clipboard();

View File

@ -131,7 +131,7 @@ export class Dialog {
/**
* Display or remove an error.
*/
public set error(error: string) {
public set error(error: string | undefined) {
while (this.errors.lastChild) {
this.errors.removeChild(this.errors.lastChild);
}

View File

@ -1,352 +1,388 @@
// import * as electron from "electron";
// import { EventEmitter } from "events";
// import * as fs from "fs";
// import { getFetchUrl } from "../src/coder/api";
// import { escapePath } from "../src/coder/common";
// import { wush } from "../src/coder/server";
// import { IKey, Dialog } from "./dialog";
/// <reference path="../../../../lib/vscode/src/typings/electron.d.ts" />
import { exec } from "child_process";
import { EventEmitter } from "events";
import * as fs from "fs";
import { promisify } from "util";
import { logger, field } from "@coder/logger";
import { escapePath } from "@coder/protocol";
import { IKey, Dialog as DialogBox } from "./dialog";
import { clipboard } from "./clipboard";
// (global as any).getOpenUrls = () => {
// return [];
// };
// tslint:disable-next-line no-any
(global as any).getOpenUrls = (): string[] => {
return [];
};
// const oldCreateElement = document.createElement;
if (typeof document === "undefined") {
(<any>global).document = {} as any;
}
// document.createElement = (tagName: string) => {
// const createElement = (tagName: string) => {
// return oldCreateElement.call(document, tagName);
// };
const oldCreateElement: <K extends keyof HTMLElementTagNameMap>(
tagName: K, options?: ElementCreationOptions,
) => HTMLElementTagNameMap[K] = document.createElement;
// if (tagName === "webview") {
// const view = createElement("iframe") as HTMLIFrameElement;
// view.style.border = "0px";
// const frameID = Math.random().toString();
// view.addEventListener("error", (event) => {
// console.log("Got iframe error", event.error, event.message);
// });
// window.addEventListener("message", (event) => {
// if (!event.data || !event.data.id) {
// return;
// }
// if (event.data.id !== frameID) {
// return;
// }
// const e = new CustomEvent("ipc-message");
// (e as any).channel = event.data.channel;
// (e as any).args = event.data.data;
// view.dispatchEvent(e);
// });
// view.sandbox.add("allow-same-origin", "allow-scripts", "allow-popups", "allow-forms");
// Object.defineProperty(view, "preload", {
// set: (url: string) => {
// view.onload = () => {
// view.contentDocument.body.id = frameID;
// view.contentDocument.body.parentElement.style.overflow = "hidden";
// const script = document.createElement("script");
// script.src = url;
// view.contentDocument.head.appendChild(script);
// };
// },
// });
// (view as any).getWebContents = () => undefined;
// (view as any).send = (channel: string, ...args) => {
// if (args[0] && typeof args[0] === "object" && args[0].contents) {
// args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
// args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
// }
// view.contentWindow.postMessage({
// channel,
// data: args,
// id: frameID,
// }, "*");
// };
// return view;
// }
const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K] => {
const createElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K] => {
return oldCreateElement.call(document, tagName);
};
// return createElement(tagName);
// };
if (tagName === "webview") {
const view = createElement("iframe") as HTMLIFrameElement;
view.style.border = "0px";
const frameID = Math.random().toString();
view.addEventListener("error", (event) => {
logger.error("iframe error", field("event", event));
});
window.addEventListener("message", (event) => {
if (!event.data || !event.data.id) {
return;
}
if (event.data.id !== frameID) {
return;
}
const e = new CustomEvent("ipc-message");
(e as any).channel = event.data.channel; // tslint:disable-line no-any
(e as any).args = event.data.data; // tslint:disable-line no-any
view.dispatchEvent(e);
});
view.sandbox.add("allow-same-origin", "allow-scripts", "allow-popups", "allow-forms");
Object.defineProperty(view, "preload", {
set: (url: string): void => {
view.onload = (): void => {
if (view.contentDocument) {
view.contentDocument.body.id = frameID;
view.contentDocument.body.parentElement!.style.overflow = "hidden";
const script = document.createElement("script");
script.src = url;
view.contentDocument.head.appendChild(script);
}
};
},
});
(view as any).getWebContents = (): void => undefined; // tslint:disable-line no-any
(view as any).send = (channel: string, ...args: any[]): void => { // tslint:disable-line no-any
if (args[0] && typeof args[0] === "object" && args[0].contents) {
// TODO
// args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
// args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
}
if (view.contentWindow) {
view.contentWindow.postMessage({
channel,
data: args,
id: frameID,
}, "*");
}
};
// const rendererToMainEmitter = new EventEmitter();
// const mainToRendererEmitter = new EventEmitter();
return view;
}
// module.exports = {
// clipboard: {
// has: () => {
// return false;
// },
// writeText: (value: string) => {
// // Taken from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
// const active = document.activeElement as HTMLElement;
// const el = document.createElement('textarea'); // Create a <textarea> element
// el.value = value; // Set its value to the string that you want copied
// el.setAttribute('readonly', ''); // Make it readonly to be tamper-proof
// el.style.position = 'absolute';
// el.style.left = '-9999px'; // Move outside the screen to make it invisible
// document.body.appendChild(el); // Append the <textarea> element to the HTML document
// const selected =
// document.getSelection().rangeCount > 0 // Check if there is any content selected previously
// ? document.getSelection().getRangeAt(0) // Store selection if found
// : false; // Mark as false to know no selection existed before
// el.select(); // Select the <textarea> content
// document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
// document.body.removeChild(el); // Remove the <textarea> element
// if (selected) { // If a selection existed before copying
// document.getSelection().removeAllRanges(); // Unselect everything on the HTML document
// document.getSelection().addRange(selected); // Restore the original selection
// }
// active.focus();
// },
// },
// dialog: {
// showSaveDialog: (_: void, options: Electron.SaveDialogOptions, callback: (filename: string) => void): void => {
// const defaultPath = options.defaultPath || "/untitled";
// const fileIndex = defaultPath.lastIndexOf("/");
// const extensionIndex = defaultPath.lastIndexOf(".");
// const saveDialogOptions = {
// buttons: ["Cancel", "Save"],
// detail: "Enter a path for this file",
// input: {
// value: defaultPath,
// selection: {
// start: fileIndex === -1 ? 0 : fileIndex + 1,
// end: extensionIndex === -1 ? defaultPath.length : extensionIndex,
// },
// },
// message: "Save file",
// };
return createElement(tagName);
};
// const dialog = new Dialog(saveDialogOptions);
// dialog.onAction((action) => {
// if (action.key !== IKey.Enter && action.buttonIndex !== 1) {
// dialog.hide();
// return callback(undefined);
// }
document.createElement = newCreateElement;
// const filePath = dialog.inputValue.replace(/\/+$/, "");
// const split = filePath.split("/");
// const fileName = split.pop();
// const parentName = split.pop() || "/";
// if (fileName === "") {
// dialog.error = "You must enter a file name.";
// return;
// }
class Clipboard {
// fs.stat(filePath, (error, stats) => {
// if (error && error.code === "ENOENT") {
// dialog.hide();
// callback(filePath);
// } else if (error) {
// dialog.error = error.message;
// } else if (stats.isDirectory()) {
// dialog.error = `A directory named "${fileName}" already exists.`;
// } else {
// dialog.error = undefined;
public has(): boolean {
return false;
}
// const confirmDialog = new Dialog({
// message: `A file named "${fileName}" already exists. Do you want to replace it?`,
// detail: `The file already exists in "${parentName}". Replacing it will overwrite its contents.`,
// buttons: ["Cancel", "Replace"],
// });
public writeText(value: string): Promise<void> {
return clipboard.writeText(value);
}
// confirmDialog.onAction((action) => {
// if (action.buttonIndex === 1) {
// confirmDialog.hide();
// return callback(filePath);
// }
}
// confirmDialog.hide();
// dialog.show();
// });
class Shell {
// dialog.hide();
// confirmDialog.show();
// }
// });
// });
// dialog.show();
// },
// showOpenDialog: () => {
// console.log("Trying to show the open dialog");
// },
// showMessageBox: (_: void, options: Electron.MessageBoxOptions, callback: (button: number, checked: boolean) => void): void => {
// const dialog = new Dialog(options);
// dialog.onAction((action) => {
// dialog.hide();
// callback(action.buttonIndex, false);
// });
// dialog.show();
// },
// },
// remote: {
// dialog: {
// showOpenDialog: () => {
// console.log("Trying to remotely open");
// },
// },
// },
// webFrame: {
// getZoomFactor: () => {
// return 1;
// },
// getZoomLevel: () => {
// return 1;
// },
// setZoomLevel: () => {
// return;
// },
// },
// screen: {
// getAllDisplays: () => {
// return [{
// bounds: {
// x: 1000,
// y: 1000,
// },
// }];
// },
// },
// app: {
// isAccessibilitySupportEnabled: () => {
// return false;
// },
// setAsDefaultProtocolClient: () => {
public async moveItemToTrash(path: string): Promise<void> {
await promisify(exec)(
`trash-put --trash-dir ${escapePath("~/.Trash")} ${escapePath(path)}`,
);
}
// },
// send: (str) => {
// console.log("APP Trying to send", str);
// //
// },
// on: () => {
// //
// },
// once: () => {
// //
// },
// },
// // ipcRenderer communicates with ipcMain
// ipcRenderer: {
// send: (str, ...args) => {
// rendererToMainEmitter.emit(str, {
// sender: module.exports.ipcMain,
// }, ...args);
// },
// on: (str, listener) => {
// mainToRendererEmitter.on(str, listener);
// },
// once: (str, listener) => {
// mainToRendererEmitter.once(str, listener);
// },
// removeListener: (str, listener) => {
// mainToRendererEmitter.removeListener(str, listener);
// },
// },
// ipcMain: {
// send: (str, ...args) => {
// mainToRendererEmitter.emit(str, {
// sender: module.exports.ipcRenderer,
// }, ...args);
// },
// on: (str, listener) => {
// rendererToMainEmitter.on(str, listener);
// },
// once: (str, listener) => {
// rendererToMainEmitter.once(str, listener);
// },
// },
// shell: {
// moveItemToTrash: async (path) => {
// const response = await wush.execute({
// command: `trash-put --trash-dir ${escapePath("~/.Trash")} ${escapePath(path)}`,
// }).done();
// return response.wasSuccessful();
// },
// },
// BrowserWindow: class {
}
// public webContents = {
// on: () => {
class App extends EventEmitter {
// },
// session: {
// webRequest: {
// onBeforeRequest: () => {
public isAccessibilitySupportEnabled(): boolean {
return false;
}
// },
public setAsDefaultProtocolClient(): void {
throw new Error("not implemented");
}
// onBeforeSendHeaders: () => {
}
// },
class Dialog {
// onHeadersReceived: () => {
public showSaveDialog(_: void, options: Electron.SaveDialogOptions, callback: (filename: string | undefined) => void): void {
const defaultPath = options.defaultPath || "/untitled";
const fileIndex = defaultPath.lastIndexOf("/");
const extensionIndex = defaultPath.lastIndexOf(".");
const saveDialogOptions = {
buttons: ["Cancel", "Save"],
detail: "Enter a path for this file",
input: {
value: defaultPath,
selection: {
start: fileIndex === -1 ? 0 : fileIndex + 1,
end: extensionIndex === -1 ? defaultPath.length : extensionIndex,
},
},
message: "Save file",
};
// },
// }
// },
// removeAllListeners: () => {
const dialog = new DialogBox(saveDialogOptions);
dialog.onAction((action) => {
if (action.key !== IKey.Enter && action.buttonIndex !== 1) {
dialog.hide();
// },
// }
return callback(undefined);
}
// public static getFocusedWindow() {
// return undefined;
// }
const inputValue = dialog.inputValue || "";
const filePath = inputValue.replace(/\/+$/, "");
const split = filePath.split("/");
const fileName = split.pop();
const parentName = split.pop() || "/";
if (fileName === "") {
dialog.error = "You must enter a file name.";
// public isMaximized() {
// return false;
// }
return;
}
// public isFullScreen() {
// return false;
// }
fs.stat(filePath, (error, stats) => {
if (error && error.code === "ENOENT") {
dialog.hide();
callback(filePath);
} else if (error) {
dialog.error = error.message;
} else if (stats.isDirectory()) {
dialog.error = `A directory named "${fileName}" already exists.`;
} else {
dialog.error = undefined;
// public setMenuBarVisibility(visibility) {
// console.log("We are setting the menu bar to ", visibility);
// }
const confirmDialog = new DialogBox({
message: `A file named "${fileName}" already exists. Do you want to replace it?`,
detail: `The file already exists in "${parentName}". Replacing it will overwrite its contents.`,
buttons: ["Cancel", "Replace"],
});
// public setAutoHideMenuBar() {
confirmDialog.onAction((action) => {
if (action.buttonIndex === 1) {
confirmDialog.hide();
// }
return callback(filePath);
}
// public on() {
confirmDialog.hide();
dialog.show();
});
// }
dialog.hide();
confirmDialog.show();
}
});
});
dialog.show();
}
// public setTitle(value: string): void {
// document.title = value;
// }
// },
// toggleFullScreen: () => {
// const doc = document as any;
// const isInFullScreen = doc.fullscreenElement
// || doc.webkitFullscreenElement
// || doc.mozFullScreenElement
// || doc.msFullscreenElement;
public showOpenDialog(): void {
throw new Error("not implemented");
}
// const body = doc.body;
// if (!isInFullScreen) {
// if (body.requestFullscreen) {
// body.requestFullscreen();
// } else if (body.mozRequestFullScreen) {
// body.mozRequestFullScreen();
// } else if (body.webkitRequestFullScreen) {
// body.webkitRequestFullScreen();
// } else if (body.msRequestFullscreen) {
// body.msRequestFullscreen();
// }
// } else {
// if (doc.exitFullscreen) {
// doc.exitFullscreen();
// } else if (doc.webkitExitFullscreen) {
// doc.webkitExitFullscreen();
// } else if (doc.mozCancelFullScreen) {
// doc.mozCancelFullScreen();
// } else if (doc.msExitFullscreen) {
// doc.msExitFullscreen();
// }
// }
// },
// focusWindow: () => {
// console.log("focusing window");
// window.focus();
// },
// };
public showMessageBox(_: void, options: Electron.MessageBoxOptions, callback: (button: number | undefined, checked: boolean) => void): void {
const dialog = new DialogBox(options);
dialog.onAction((action) => {
dialog.hide();
callback(action.buttonIndex, false);
});
dialog.show();
}
}
class WebFrame {
public getZoomFactor(): number {
return 1;
}
public getZoomLevel(): number {
return 1;
}
public setZoomLevel(): void {
// Nothing.
}
}
class Screen {
public getAllDisplays(): [] {
return [];
}
}
class WebRequest extends EventEmitter {
public onBeforeRequest(): void {
throw new Error("not implemented");
}
public onBeforeSendHeaders(): void {
throw new Error("not implemented");
}
public onHeadersReceived(): void {
throw new Error("not implemented");
}
}
class Session extends EventEmitter {
public webRequest = new WebRequest();
public resolveProxy(url: string, callback: (proxy: string) => void): void {
// TODO: not sure what this actually does.
callback(url);
}
}
class WebContents extends EventEmitter {
public session = new Session();
}
class BrowserWindow extends EventEmitter {
public webContents = new WebContents();
private representedFilename: string = "";
public static getFocusedWindow(): undefined {
return undefined;
}
public focus(): void {
window.focus();
}
public show(): void {
window.focus();
}
public reload(): void {
location.reload();
}
public isMaximized(): boolean {
return false;
}
public setFullScreen(fullscreen: boolean): void {
if (fullscreen) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}
public isFullScreen(): boolean {
return document.fullscreenEnabled;
}
public isFocused(): boolean {
return document.hasFocus();
}
public setMenuBarVisibility(): void {
throw new Error("not implemented");
}
public setAutoHideMenuBar(): void {
throw new Error("not implemented");
}
public setRepresentedFilename(filename: string): void {
this.representedFilename = filename;
}
public getRepresentedFilename(): string {
return this.representedFilename;
}
public setTitle(value: string): void {
document.title = value;
}
}
/**
* We won't be able to do a 1 to 1 fill because things like moveItemToTrash for
* example returns a boolean while we need a promise.
*/
class ElectronFill {
public readonly shell = new Shell();
public readonly clipboard = new Clipboard();
public readonly app = new App();
public readonly dialog = new Dialog();
public readonly webFrame = new WebFrame();
public readonly screen = new Screen();
private readonly rendererToMainEmitter = new EventEmitter();
private readonly mainToRendererEmitter = new EventEmitter();
public get BrowserWindow(): typeof BrowserWindow {
return BrowserWindow;
}
// tslint:disable no-any
public get ipcRenderer(): object {
return {
send: (str: string, ...args: any[]): void => {
this.rendererToMainEmitter.emit(str, {
sender: module.exports.ipcMain,
}, ...args);
},
on: (str: string, listener: (...args: any[]) => void): void => {
this.mainToRendererEmitter.on(str, listener);
},
once: (str: string, listener: (...args: any[]) => void): void => {
this.mainToRendererEmitter.once(str, listener);
},
removeListener: (str: string, listener: (...args: any[]) => void): void => {
this.mainToRendererEmitter.removeListener(str, listener);
},
};
}
public get ipcMain(): object {
return {
send: (str: string, ...args: any[]): void => {
this.mainToRendererEmitter.emit(str, {
sender: module.exports.ipcRenderer,
}, ...args);
},
on: (str: string, listener: (...args: any[]) => void): void => {
this.rendererToMainEmitter.on(str, listener);
},
once: (str: string, listener: (...args: any[]) => void): void => {
this.rendererToMainEmitter.once(str, listener);
},
};
}
// tslint:enable no-any
}
module.exports = new ElectronFill();

View File

@ -1 +1 @@
module.exports = {};
export = {};

View File

@ -0,0 +1,38 @@
import { InitData } from "@coder/protocol";
import { client } from "./client";
class OS {
private _homedir: string | undefined;
private _tmpdir: string | undefined;
public constructor() {
client.initData.then((data) => {
this.initialize(data);
});
}
public homedir(): string {
if (typeof this._homedir === "undefined") {
throw new Error("not initialized");
}
return this._homedir;
}
public tmpdir(): string {
if (typeof this._tmpdir === "undefined") {
throw new Error("not initialized");
}
return this._tmpdir;
}
public initialize(data: InitData): void {
this._homedir = data.homeDirectory;
this._tmpdir = data.tmpDirectory;
}
}
export = new OS();

View File

@ -1,5 +1,4 @@
import { implementation as promisify } from "util.promisify";
export * from "../../../../node_modules/util";
import { implementation } from "util.promisify";
export {
promisify,
}
export const promisify = implementation;