1
0
mirror of https://git.tuxpa.in/a/code-server.git synced 2024-12-28 21:25:25 +00:00

Delete all the things

This commit is contained in:
Asher 2019-07-02 14:29:41 -05:00
parent f25a614333
commit 310bfe509e
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
242 changed files with 0 additions and 31959 deletions

View File

@ -1,42 +0,0 @@
/**
* Script that detects platform name and arch.
* Cannot use os.platform() as that won't detect libc version
*/
import * as cp from "child_process";
import * as fs from "fs";
import * as os from "os";
enum Lib {
GLIBC,
MUSL,
}
const CLIB: Lib | undefined = ((): Lib | undefined => {
if (os.platform() !== "linux") {
return;
}
const glibc = cp.spawnSync("getconf", ["GNU_LIBC_VERSION"]);
if (glibc.status === 0) {
return Lib.GLIBC;
}
const ldd = cp.spawnSync("ldd", ["--version"]);
if (ldd.stdout && ldd.stdout.indexOf("musl") !== -1) {
return Lib.MUSL;
}
const muslFile = fs.readdirSync("/lib").find((value) => value.startsWith("libc.musl"));
if (muslFile) {
return Lib.MUSL;
}
return Lib.GLIBC;
})();
export const platform = (): NodeJS.Platform | "musl" => {
if (CLIB === Lib.MUSL) {
return "musl";
}
return os.platform();
};

View File

@ -1,211 +0,0 @@
import { register, run } from "@coder/runner";
import { logger, field } from "@coder/logger";
import * as fs from "fs";
import * as fse from "fs-extra";
import * as os from "os";
import { platform } from "./platform";
import * as path from "path";
import * as zlib from "zlib";
import * as https from "https";
import * as tar from "tar";
const isWin = os.platform() === "win32";
const libPath = path.join(__dirname, "../lib");
const vscodePath = path.join(libPath, "vscode");
const defaultExtensionsPath = path.join(libPath, "extensions");
const pkgsPath = path.join(__dirname, "../packages");
const vscodeVersion = process.env.VSCODE_VERSION || "1.33.1";
const vsSourceUrl = `https://codesrv-ci.cdr.sh/vstar-${vscodeVersion}.tar.gz`;
const buildServerBinary = register("build:server:binary", async (runner) => {
logger.info("Building with environment", field("env", {
NODE_ENV: process.env.NODE_ENV,
VERSION: process.env.VERSION,
OSTYPE: process.env.OSTYPE,
TARGET: process.env.TARGET,
}));
await ensureInstalled();
await Promise.all([
buildBootstrapFork(),
buildWeb(),
buildServerBundle(),
buildAppBrowser(),
]);
await buildServerBinaryPackage();
});
const buildServerBinaryPackage = register("build:server:binary:package", async (runner) => {
const cliPath = path.join(pkgsPath, "server");
runner.cwd = cliPath;
if (!fs.existsSync(path.join(cliPath, "out"))) {
throw new Error("Cannot build binary without server bundle built");
}
await buildServerBinaryCopy();
const resp = await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build:binary"]);
if (resp.exitCode !== 0) {
throw new Error(`Failed to package binary: ${resp.stderr}`);
}
});
const buildServerBinaryCopy = register("build:server:binary:copy", async (runner) => {
const cliPath = path.join(pkgsPath, "server");
const cliBuildPath = path.join(cliPath, "build");
fse.removeSync(cliBuildPath);
fse.mkdirpSync(path.join(cliBuildPath, "extensions"));
const bootstrapForkPath = path.join(pkgsPath, "vscode", "out", "bootstrap-fork.js");
const webOutputPath = path.join(pkgsPath, "web", "out");
const browserAppOutputPath = path.join(pkgsPath, "app", "browser", "out");
let ripgrepPath = path.join(pkgsPath, "..", "lib", "vscode", "node_modules", "vscode-ripgrep", "bin", "rg");
if (isWin) {
ripgrepPath += ".exe";
}
if (!fs.existsSync(webOutputPath)) {
throw new Error("Web bundle must be built");
}
if (!fs.existsSync(defaultExtensionsPath)) {
throw new Error("Default extensions must be built");
}
if (!fs.existsSync(bootstrapForkPath)) {
throw new Error("Bootstrap fork must exist");
}
if (!fs.existsSync(ripgrepPath)) {
throw new Error("Ripgrep must exist");
}
fse.copySync(defaultExtensionsPath, path.join(cliBuildPath, "extensions"));
fs.writeFileSync(path.join(cliBuildPath, "bootstrap-fork.js.gz"), zlib.gzipSync(fs.readFileSync(bootstrapForkPath)));
const cpDir = (dir: string, rootPath: string, subdir?: "login"): void => {
const stat = fs.statSync(dir);
if (stat.isDirectory()) {
const paths = fs.readdirSync(dir);
paths.forEach((p) => cpDir(path.join(dir, p), rootPath, subdir));
} else if (stat.isFile()) {
const newPath = path.join(cliBuildPath, "web", subdir || "", path.relative(rootPath, dir));
fse.mkdirpSync(path.dirname(newPath));
fs.writeFileSync(newPath + ".gz", zlib.gzipSync(fs.readFileSync(dir)));
} else {
// Nothing
}
};
cpDir(webOutputPath, webOutputPath);
cpDir(browserAppOutputPath, browserAppOutputPath, "login");
fse.mkdirpSync(path.join(cliBuildPath, "dependencies"));
fse.copySync(ripgrepPath, path.join(cliBuildPath, "dependencies", "rg"));
});
const buildServerBundle = register("build:server:bundle", async (runner) => {
const cliPath = path.join(pkgsPath, "server");
runner.cwd = cliPath;
await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]);
});
const buildBootstrapFork = register("build:bootstrap-fork", async (runner) => {
await ensureInstalled();
await ensurePatched();
const vscodePkgPath = path.join(pkgsPath, "vscode");
runner.cwd = vscodePkgPath;
await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build:bootstrap-fork"]);
});
const buildAppBrowser = register("build:app:browser", async (runner) => {
await ensureInstalled();
const appPath = path.join(pkgsPath, "app/browser");
runner.cwd = appPath;
fse.removeSync(path.join(appPath, "out"));
await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]);
});
const buildWeb = register("build:web", async (runner) => {
await ensureInstalled();
await ensurePatched();
const webPath = path.join(pkgsPath, "web");
runner.cwd = webPath;
fse.removeSync(path.join(webPath, "out"));
await runner.execute(isWin ? "npm.cmd" : "npm", ["run", "build"]);
});
const ensureInstalled = register("vscode:install", async (runner) => {
runner.cwd = libPath;
if (fs.existsSync(vscodePath) && fs.existsSync(defaultExtensionsPath)) {
const pkgVersion = JSON.parse(fs.readFileSync(path.join(vscodePath, "package.json")).toString("utf8")).version;
if (pkgVersion === vscodeVersion) {
runner.cwd = vscodePath;
const reset = await runner.execute("git", ["reset", "--hard"]);
if (reset.exitCode !== 0) {
throw new Error(`Failed to clean git repository: ${reset.stderr}`);
}
return;
}
}
fse.removeSync(libPath);
fse.mkdirpSync(libPath);
await new Promise<void>((resolve, reject): void => {
https.get(vsSourceUrl, (res) => {
if (res.statusCode !== 200) {
return reject(res.statusMessage);
}
res.pipe(tar.x({
C: libPath,
}).on("finish", () => {
resolve();
}).on("error", (err: Error) => {
reject(err);
}));
}).on("error", (err) => {
reject(err);
});
});
});
const ensurePatched = register("vscode:patch", async (runner) => {
if (!fs.existsSync(vscodePath)) {
throw new Error("vscode must be cloned to patch");
}
await ensureInstalled();
runner.cwd = vscodePath;
const patchPath = path.join(__dirname, "../scripts/vscode.patch");
const apply = await runner.execute("git", ["apply", "--unidiff-zero", patchPath]);
if (apply.exitCode !== 0) {
throw new Error(`Failed to apply patches: ${apply.stderr}`);
}
});
register("package", async (runner, releaseTag) => {
if (!releaseTag) {
throw new Error("Please specify the release tag.");
}
const releasePath = path.resolve(__dirname, "../release");
const archiveName = `code-server${releaseTag}-${platform()}-${os.arch()}`;
const archiveDir = path.join(releasePath, archiveName);
fse.removeSync(archiveDir);
fse.mkdirpSync(archiveDir);
const binaryPath = path.join(__dirname, `../packages/server/cli-${platform()}-${os.arch()}`);
const binaryDestination = path.join(archiveDir, "code-server");
fse.copySync(binaryPath, binaryDestination);
fs.chmodSync(binaryDestination, "755");
["README.md", "LICENSE"].forEach((fileName) => {
fse.copySync(path.resolve(__dirname, `../${fileName}`), path.join(archiveDir, fileName));
});
runner.cwd = releasePath;
await (os.platform() === "linux"
? runner.execute("tar", ["-cvzf", `${archiveName}.tar.gz`, `${archiveName}`])
: runner.execute("zip", ["-r", `${archiveName}.zip`, `${archiveName}`]));
});
run();

View File

@ -1,12 +0,0 @@
{
"name": "@coder/app",
"scripts": {
"start": "node ../../../node_modules/webpack-dev-server/bin/webpack-dev-server.js --config ./webpack.config.js",
"build": "node ../../../node_modules/webpack/bin/webpack.js --config ./webpack.config.js"
},
"dependencies": {
"@material/checkbox": "^0.44.1",
"@material/textfield": "^0.44.1",
"material-components-web": "^0.44.0"
}
}

View File

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<title>Authenticate: code-server</title>
</head>
<body>
<form id="login-form">
<div class="login">
<div class="back">
<- Back </div> <h4 class="title">code-server</h4>
<h2 class="subtitle">
Enter server password
</h2>
<div class="mdc-text-field">
<input type="password" id="password" class="mdc-text-field__input" required>
<label class="mdc-floating-label" for="password">Password</label>
<div class="mdc-line-ripple"></div>
</div>
<button id="submit" class="mdc-button mdc-button--unelevated">
<span class="mdc-button__label">Enter IDE</span>
</button>
<div id="error-display"></div>
</div>
</form>
</body>
</html>

View File

@ -1,121 +0,0 @@
@import url("https://use.typekit.net/vzk7ygg.css");
html, body {
background-color: #FFFFFF;
min-height: 100%;
}
body {
font-family: 'aktiv-grotesk';
display: flex;
align-items: center;
justify-content: center;
height: calc(100vh - 20px);
margin: 0;
padding: 10px;
--mdc-theme-primary: #AAADA1;
--mdc-theme-secondary: #AAADA1;
&.in-app {
.back {
pointer-events: all;
opacity: 1;
}
}
}
.login {
box-shadow: 0 18px 80px 10px rgba(69, 65, 78, 0.08);
max-width: 328px;
width: 100%;
padding: 40px;
border-radius: 5px;
position: relative;
color: #575962;
.title {
margin-bottom: 0px;
font-size: 12px;
font-weight: 500;
letter-spacing: 1.5px;
line-height: 15px;
margin-bottom: 5px;
margin-top: 0px;
text-align: center;
text-transform: uppercase;
}
.subtitle {
text-align: center;
margin: 0;
font-size: 19px;
font-weight: bold;
line-height: 25px;
margin-bottom: 45px;
}
.mdc-text-field {
width: 100%;
background: none !important;
&::before {
background: none !important;
}
}
.mdc-form-field {
text-align: left;
font-size: 12px;
color: #797E84;
margin-top: 16px;
}
.mdc-button {
border-radius: 24px;
padding-left: 75px;
padding-right: 75px;
padding-top: 15px;
padding-bottom: 15px;
height: 48px;
margin: 0 auto;
display: block;
box-shadow: 0 12px 17px 2px rgba(171,173,163,0.14), 0 5px 22px 4px rgba(171,173,163,0.12), 0 7px 8px -4px rgba(171,173,163,0.2);
margin-top: 40px;
}
}
.mdc-text-field--focused:not(.mdc-text-field--disabled) .mdc-floating-label {
color: var(--mdc-theme-primary);
}
.mdc-floating-label--float-above {
transform: translateY(-70%) scale(0.75);
}
.mdc-text-field:not(.mdc-text-field--disabled):not(.mdc-text-field--outlined):not(.mdc-text-field--textarea) .mdc-text-field__input, .mdc-text-field:not(.mdc-text-field--disabled):not(.mdc-text-field--outlined):not(.mdc-text-field--textarea) .mdc-text-field__input:hover {
border-bottom-color: #EBEDF2;
}
.back {
position: absolute;
top: -50px;
left: -50px;
font-weight: bold;
opacity: 0;
pointer-events: none;
// transition: 500ms opacity ease;
}
#error-display {
box-sizing: border-box;
color: #bb2d0f;
font-size: 14px;
font-weight: 400;
letter-spacing: 0.3px;
line-height: 12px;
padding: 8px;
padding-bottom: 0;
padding-top: 20px;
text-align: center;
}

View File

@ -1,46 +0,0 @@
//@ts-ignore
import { MDCTextField } from "@material/textfield";
//@ts-ignore
import { MDCCheckbox } from "@material/checkbox";
import "material-components-web/dist/material-components-web.css";
import "./app.scss";
document.querySelectorAll(".mdc-text-field").forEach((d) => new MDCTextField(d));
document.querySelectorAll(".mdc-checkbox").forEach((d) => new MDCCheckbox(d));
window.addEventListener("message", (event) => {
if (event.data === "app") {
document.body.classList.add("in-app");
const back = document.querySelector(".back")!;
back.addEventListener("click", () => {
(event.source as Window).postMessage("back", event.origin);
});
}
});
const password = document.getElementById("password") as HTMLInputElement;
const form = document.getElementById("login-form") as HTMLFormElement;
if (!form) {
throw new Error("No password form found");
}
form.addEventListener("submit", (e) => {
e.preventDefault();
document.cookie = `password=${password.value}; `
+ `path=${location.pathname.replace(/\/login\/?$/, "/")}; `
+ `domain=${location.hostname}`;
location.reload();
});
/**
* Notify user on load of page if previous password was unsuccessful
*/
const reg = new RegExp(`password=(\\w+);?`);
const matches = document.cookie.match(reg);
const errorDisplay = document.getElementById("error-display") as HTMLDivElement;
if (document.referrer === document.location.href && matches) {
errorDisplay.innerText = "Password is incorrect!";
}

View File

@ -1,16 +0,0 @@
const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const root = path.resolve(__dirname, "../../..");
module.exports = merge(
require(path.join(root, "scripts/webpack.client.config.js"))({
dirname: __dirname,
entry: path.join(__dirname, "src/app.ts"),
name: "login",
template: path.join(__dirname, "src/app.html"),
}), {
},
);

View File

@ -1,606 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@material/animation@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/animation/-/animation-0.41.0.tgz#315b45b32e1aeebee8a4cf555b8ad52076d09ddd"
integrity sha512-yYAwJbX3Q2AFd4dr6IYOsWLQy2HN8zWOFVl9AbUXunjzTfJCa/ecfXCriaT6qkmoNoHeTdJHRrsQJZC5GsPvzA==
"@material/auto-init@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/auto-init/-/auto-init-0.41.0.tgz#8a59bb0b83e0f51ead9508074f9a29b2b6a20eec"
integrity sha512-jp6L8MpYu7DudgDfA8iTyD9BwQrYPEDsIJGbqzN9vcCBl5FoBatkB8pcFXKr+1mRBk7T1Qmf6+H5nDtxyXjHEQ==
"@material/base@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/base/-/base-0.41.0.tgz#badadce711b4c25b1eb889a5e7581e32cd07c421"
integrity sha512-tEyzwBRu3d1H120SfKsDVYZHcqT5lKohh/7cWKR93aAaPDkSvjpKJIjyu2yuSkjpDduVZGzVocYbOvhUKhhzXQ==
"@material/button@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/button/-/button-0.44.0.tgz#f01dcbea88bdc314e7640b76e5558101c8b4d69d"
integrity sha512-T8u8s8rlB49D9/5Nh5b0XsKRgSq3X0yWGo71MgaTnCnwxt8oZ6PxW/cH6Nn3Xp0NCr3mlSVQs08BviUfAmtlsg==
dependencies:
"@material/elevation" "^0.44.0"
"@material/feature-targeting" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/card@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/card/-/card-0.44.0.tgz#e62050e3e77f525173a015119200055cd7b71bf0"
integrity sha512-fUixXuh133bVp5c1gPIHreL5jwMJEeVIQf0E4xdxhkA+i4ku8fIAvIW62EuCmfJsXicv4q8NG3Ip6pCY+NW3ZA==
dependencies:
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/checkbox@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-0.44.0.tgz#5d0eee1db006db9f0fb700bf1c20408292305cf7"
integrity sha512-IzucxG+NuPNyByGmHg/cuYJ5ooMKouuj994PZXZyqb7owfrjjtXm7wjav66cvCowbVbcoa1owQMGBi18C9f4TQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/checkbox@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-0.44.1.tgz#7e69271ccab7c57914a475da3a15d4d36702c1c4"
integrity sha512-RFUNc+9RKRozL+gXfJ8V57tXfC31Q9R9tRMTHpe62NXZriTrwNJDnSkFIERDXqtMGtkKUnIlPfPE5znF6XyPUw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/feature-targeting" "^0.44.1"
"@material/ripple" "^0.44.1"
"@material/rtl" "^0.42.0"
"@material/selection-control" "^0.44.1"
"@material/theme" "^0.43.0"
"@material/chips@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/chips/-/chips-0.44.0.tgz#bf553a5bf5db7320978402ac92069c9688b84d5a"
integrity sha512-+qrme6sGwYmX/ixHAo3Z1M7lorsxRyKexn1l+BSBX5PBc2f4w5Ml1eYYYcyVGfLX9LXmefRk0G6dUXXPyCE00g==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/checkbox" "^0.44.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/dialog@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/dialog/-/dialog-0.44.0.tgz#388f93f9f225824c75cbe9da8c464a52d79972e8"
integrity sha512-V6ButfknOMKOscL0Y39yLjamxvrIuyugobjf5s44ZeJc+9jUSkC7M3zP+T7rh358NcX+JSPP8iCGmUn/+LXpMQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/dom" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
focus-trap "^4.0.2"
"@material/dom@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/dom/-/dom-0.41.0.tgz#6756865f97bad4c91ee75e69d769d7cdc25398ae"
integrity sha512-wOJrMwjPddYXpQFZAIaCLWI3TO/6KU1lxESTBzunni8A4FHQVWhokml5Xt85GqZwmPFeIF2s+D0wfbWyrGBuKQ==
"@material/drawer@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/drawer/-/drawer-0.44.0.tgz#74b3ddfb741bffc72331c7a73cf62716fd3f0ab3"
integrity sha512-AYwFe0jgqqSmJd1bny8JJTA2SScF86Wfbk99lXXEwd/acS8IbnnuH6zfAg6MyJX12FDb8dE8Z/Ok1IwLiVa9sQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/list" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
focus-trap "^4.0.2"
"@material/elevation@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-0.44.0.tgz#ca16a67188ce9810dc2fa3d7a39073e72df4b754"
integrity sha512-edNou34yFCSMb6XXe/6Y7AEh8DigWAhBUyIeMiMBD4k1km2xYCJbcnl8FBPJFteOrca97KoJComRlJPB6EurRQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/fab@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/fab/-/fab-0.44.0.tgz#0bcbbdfb6f24c53d59e08c9c0d400d2616dea184"
integrity sha512-1CEP4NlXDYioJ/YpSjh/MlIygtoC7CaHqIbucxX1O5WRPmS7K1uPt+o7netbLErAmcJdV/JrI/tqh9kKuX2x/Q==
dependencies:
"@material/animation" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/feature-targeting@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-0.44.0.tgz#52cc73f0c8a83159de0357aebe74f15f9856fb4c"
integrity sha512-ShuC2TOLfjFpYUCQFtvkqDJhM6HTaucSx5HkRbOvOG+VlpzDx6pAqRUmdVaq2p7tHoQf2vwPMlSVm3gOjWt4VQ==
"@material/feature-targeting@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-0.44.1.tgz#afafc80294e5efab94bee31a187273d43d34979a"
integrity sha512-90cc7njn4aHbH9UxY8qgZth1W5JgOgcEdWdubH1t7sFkwqFxS5g3zgxSBt46TygFBVIXNZNq35Xmg80wgqO7Pg==
"@material/floating-label@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/floating-label/-/floating-label-0.44.0.tgz#8694cd49f6905641b67a9e7a112b820d028f09c7"
integrity sha512-k4npGNxyMtnjgJZNjU5VvqqaUqlbzlbVAhepT8PxYTpj+4Skg6PjHwieTCDCgsbqHvFcQX+WfUrSZXY7wFV7cw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/floating-label@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/floating-label/-/floating-label-0.44.1.tgz#39af84a3a0abbfa6d210911d5f4200a65c2ef59b"
integrity sha512-umj5q5feJcZuB8snRX5aVBrwQNnrt/HcvN7pENzgqaYZNcmBnxRl0OutTlHCn6l7OVU9VlWhFMf77DYwmMWKJQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.1"
"@material/form-field@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/form-field/-/form-field-0.44.0.tgz#b7518e885c0e953a2a5fe0140af927c30e066f4e"
integrity sha512-SK+V34dzoBCQ/CHn5nBp8BAh21Vj9p1pcok+/WpYBTeg4EphTYP2nUQLMNEN92l6zjgAYf+g9Ocj3t26HNHWqA==
dependencies:
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/grid-list@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/grid-list/-/grid-list-0.44.0.tgz#bd31d992ab1a910731e4a47c11fe91d44e3bc02b"
integrity sha512-NxLL0A48K1O14ZZymFIyf6HDbF33+NgXYXqP2yosTC3Jw4iwmUcJTpFTmSw1U/m1xT4zEpeKEGJ4vjVUWpS9Mg==
dependencies:
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/icon-button@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-0.44.0.tgz#febbcfd27d91eca8096ae042b9c07ed0f65345e9"
integrity sha512-n6L7RaRyEci6eGsuBTSEG+t9ATHAHaMlf9zuTWorEnIXY4DAmGO7ggBjw4+1XIOjhpLeIjyJdcvUK6Yz/UVM6Q==
dependencies:
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/icon-toggle@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/icon-toggle/-/icon-toggle-0.44.0.tgz#b9de32f194b5aa9721ca799d59be0f477a5c5305"
integrity sha512-8T1b4iK61/q/3U0iIjEDJ9do5viCQ45IbrQqa8EYCZ1KDU/Q8z5N+bvOzQK8XnTL51BdDRMgP9lfQZh6nszmkA==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/image-list@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/image-list/-/image-list-0.44.0.tgz#a27996962044ac8c9ce6cb509f63746f08ed2e98"
integrity sha512-kI9aKJdc1Bd02l8nRTGG1wy/lNkECScfnBmCiLQ3XjAFtRYd2eWO0Z/AVvUG3egsIZnZBxqFGGsf5Htm9E/HiQ==
dependencies:
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/layout-grid@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/layout-grid/-/layout-grid-0.41.0.tgz#2e7d3be76313e0684d573b10c2c6a88b3230d251"
integrity sha512-Sa5RNoTGgfIojqJ9E94p7/k11V6q/tGk7HwKi4AQNAPjxield0zcl3G/SbsSb8YSHoK+D+7OXDN+n11x6EqF7g==
"@material/line-ripple@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/line-ripple/-/line-ripple-0.43.0.tgz#6cb530bab53f055f3583646a21ad20c1703f3a83"
integrity sha512-sXZYW4Em5uLEnAuVsQCO+sVHsTg7J2TOTJ0+akwZFMmd2tmNicjarQdlGIE9iU7Wjm51NOoLAu6Mz+8kLg90bQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/linear-progress@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/linear-progress/-/linear-progress-0.43.0.tgz#4821424aa24c78de256e74a91d5be3df55c534d9"
integrity sha512-bqkDcob+xp1mFkyBsOkoaLgrtapmz7jznGoI3nmkqyk75EB2XQcn1H8Vr6cnp/jkF4nbKu0GdVJO3VXUFmGmrQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/list@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/list/-/list-0.44.0.tgz#cf1910e15b66759334b8618d1110fbcc72c3d326"
integrity sha512-35gkN1+XZaau9d9ngyN2x14bzkj/ajZCDm7mbWibDQy272A16j6KuFLQFA8RUQV04OgL4YPVxY87dpCn/p+uTg==
dependencies:
"@material/base" "^0.41.0"
"@material/dom" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/menu-surface@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/menu-surface/-/menu-surface-0.44.0.tgz#902c081df42859b925a5b4502791b3febf48f1ae"
integrity sha512-s49kvIlQ4H5wvMD4yeHMMqnamPod06IUagMK6Ry0oTpUANSnyeNXxa3HkScl7DMJiS8IJeV21fSLAzlZYP2PDQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/menu@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/menu/-/menu-0.44.0.tgz#776ec8a04406266a0a0a13eb140b1fd691e442cb"
integrity sha512-92XvAcv9rBW1jQ3UvwJ8zk9hbSRe/FqSuFdZ9fNPE348dCY2pbcdQfnUJTe3ycAN/I1c5frkrhx8F0II+nfbNQ==
dependencies:
"@material/base" "^0.41.0"
"@material/list" "^0.44.0"
"@material/menu-surface" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/notched-outline@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/notched-outline/-/notched-outline-0.44.0.tgz#d5a2e1d649921575a7cd2e88ee4581e4a1809573"
integrity sha512-c3nqOqUQAmW3h4zBbZVbMRdf4nNTYm0tVwXIAwmcCs5nvAthEHnzHwwFddNP7/9Wju6LZ0uqWn6xlyKly0uipw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/notched-outline@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/notched-outline/-/notched-outline-0.44.1.tgz#dba4812286ba4c20f0361e6040bf9b9cad307434"
integrity sha512-x1ZJtrrqZgXT8gYE7aRF+6hTWpX7XaKZzsuwD+e0HBsogYNNsYmkBdLjl4YwhhFuHhX8vWzgkay41GtbgQx84Q==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.1"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.44.1"
"@material/theme" "^0.43.0"
"@material/radio@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/radio/-/radio-0.44.0.tgz#f4cacdfabc7d765aa000cb34c5a37966f6d4fd6d"
integrity sha512-ar7uhlfHuSwM9JUUjpv7pLDLE0p436cCMxNTpmMjWabfvo3pMWlExvk72Oj81tBgfxY/uASLB3oj4neudXu9JQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/ripple@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-0.44.0.tgz#98920ff8ec4bf5714c97df3d190f02f8a5b476cc"
integrity sha512-MlaW4nUDgzS0JOBfsUawXyTOilr0jn+xvTVn6PEaGh2rmhNA54AhixXvdsVUWE9lfmHAsZV0AJHz2z7nunNhbQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/feature-targeting" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/ripple@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-0.44.1.tgz#79cb2ddf1f998498d877d3e3c46b50fed6f13b01"
integrity sha512-prJ1p3bR+GvwAtJgtdeIixsnRVApN3bizGnX7upKoqxsqbBDHj84JxaO8EsG9bjruG/LJu8Fb6WKKdIp2oXHTA==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/feature-targeting" "^0.44.1"
"@material/theme" "^0.43.0"
"@material/rtl@^0.42.0":
version "0.42.0"
resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-0.42.0.tgz#1836e78186c2d8b996f6fbf97adab203535335bc"
integrity sha512-VrnrKJzhmspsN8WXHuxxBZ69yM5IwhCUqWr1t1eNfw3ZEvEj7i1g3P31HGowKThIN1dc1Wh4LE14rCISWCtv5w==
"@material/select@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/select/-/select-0.44.0.tgz#8041b4fe6247d013b0f12685fbdf50aa9ff57b35"
integrity sha512-tw3/QIBLuRCT+5IXx4IPiJk7FzeGeR65JEizdRUItH8yzoIiQLs/b2i3KtHM2YBXHgeUcEBF2AOqPX2opdYhug==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.0"
"@material/line-ripple" "^0.43.0"
"@material/menu" "^0.44.0"
"@material/menu-surface" "^0.44.0"
"@material/notched-outline" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/selection-control@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/selection-control/-/selection-control-0.44.0.tgz#63d5c65a47a9f54f5a0316b5ecdb5e5f35108609"
integrity sha512-HgCAPnMVMEj4X4ILkFSifqtZ3Tcc5HkU+Lfk9g0807sCaN/qBKWkYKLH2WJUbW8uk+MXK7DgP1khtS5zzanJWA==
dependencies:
"@material/ripple" "^0.44.0"
"@material/selection-control@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/selection-control/-/selection-control-0.44.1.tgz#77a47354a4c5128fa34e3ba98d9cc26e8a92839a"
integrity sha512-Xf1ee2ZV2XJ+rK8OcOD1DZOihfU0uVRdY6iYX/Bqi8k8RXnAbLIBoh6zG3xSwjRNODNvAyHEQaS/ozEfH8eehg==
dependencies:
"@material/ripple" "^0.44.1"
"@material/shape@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/shape/-/shape-0.43.0.tgz#b877acfd8be8abc9ddcf6601eb60dd0588292415"
integrity sha512-KGnoQV4G2OQbMe5Lr5Xbk8XNlO93Qi/juxXtd2wrAfiaPmktD8ug0CwdVDOPBOmj9a0gX3Ofi9XWcoU+tLEVjg==
"@material/shape@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/shape/-/shape-0.44.1.tgz#ff4d5d42b07c5781306677bffee43234b756ea8e"
integrity sha512-8mCDQmyTEhDK+HX8Tap2Lc82QlVySlXU8zDCNkWoIn1ge+UnRezSDjE4y4P1ABegN5PrkJZPartuQ1U0ttIYXw==
dependencies:
"@material/feature-targeting" "^0.44.1"
"@material/slider@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/slider/-/slider-0.44.0.tgz#2055df894eb725e541cde50a544719c07934755b"
integrity sha512-Lnn2fdUesXX4O0UpJzveEuOj+og+dXCwhal73u3l3NXEdc/eRgYxwWdF3ww4MmCZ786EwUmjb4vIc9olN4DO3A==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/snackbar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/snackbar/-/snackbar-0.44.0.tgz#d98672b849f5f295e4fac2d474a9c80f11945518"
integrity sha512-KhCrmJm8Zu/ZZPuRCGfMKsZ0vudINlNgTjlOau0kQ/UgR1xBUvLOE8NjyXZr0RQz5obyW7xpyIWIpscn0IUeyw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/button" "^0.44.0"
"@material/dom" "^0.41.0"
"@material/icon-button" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/switch@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/switch/-/switch-0.44.0.tgz#f2cbb447437b12eb3bc7f0ec8318dbd3b4f0afce"
integrity sha512-EadCg6lHUF260R2Q/l++vXIITqacvbXlobSoewA5ib6y9BU2g7l13wL1W8xAVJNUMgFa/PyN+EKT3oCql7jZLg==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/tab-bar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/tab-bar/-/tab-bar-0.44.0.tgz#b17d791bd557b1d84892fef1a1d8b8d6fef7c6d6"
integrity sha512-kCrt05d61YXyY43SNc0dPGuqysbcLr/KRDBvzpXgE4gv2jCCVhhjAH10KPlx8pthp/UtvrYJHb34acAKEGzdHA==
dependencies:
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/tab" "^0.44.0"
"@material/tab-scroller" "^0.44.0"
"@material/tab-indicator@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/tab-indicator/-/tab-indicator-0.43.0.tgz#37fd05513ba55ae218d9068c986c2676096fd6eb"
integrity sha512-RMNMQpWYghWpM6d0ayfuHEPzTiebKG0bMthViiD6tly8PubmOT8mShNhPm8ihybhDPUOLSz+7V4QNE5wikLEYg==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/tab-scroller@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/tab-scroller/-/tab-scroller-0.44.0.tgz#82d092ed45d2ee9d82038bed318e6ff6bdc36dad"
integrity sha512-Ufd3NWBN11kY2oA7bGmTYWGP1uz2mq0tfDM0JOiqoLMgD7y3Z18kmxnpq2qkg1vi4kvix28hBYGGMfLlq9rGDA==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/tab" "^0.44.0"
"@material/tab@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/tab/-/tab-0.44.0.tgz#254b92cff99015f0bd59a86d08d3f1c4744d0742"
integrity sha512-czrbGjtKkmUS3iYBX523xT5GOkjP0h+0x9fTnw+heFNpw5dCn6cZvlj3D9ayZU+ZH93x68TFhFVBuLU5f0EBXw==
dependencies:
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/tab-indicator" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/textfield@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/textfield/-/textfield-0.44.0.tgz#277b33948ddff33f7f643323895e5a683f013601"
integrity sha512-IMBwMcE82eVU+Wifpu0t84tozvBPLCeqQELDtZNYujKg3RxaultzJLwIyGKPMZ9R4yPEpV2vgXPGKE+2/AWt0g==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.0"
"@material/line-ripple" "^0.43.0"
"@material/notched-outline" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/textfield@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/textfield/-/textfield-0.44.1.tgz#2bba41cc94e68e328683997a1acf222b643dea9c"
integrity sha512-zy+56+uqr+L9DGrdOfQjOIMdKlai/7ruyqVfqIY6ieABM7LEGsOsxHhyExQmXo9IiuFhrOceWKFa4yIb8jBsmQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.1"
"@material/line-ripple" "^0.43.0"
"@material/notched-outline" "^0.44.1"
"@material/ripple" "^0.44.1"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.44.1"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.1"
"@material/theme@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/theme/-/theme-0.43.0.tgz#6d9fa113c82e841817882172c152d60d2d203ca6"
integrity sha512-/zndZL6EihI18v2mYd4O8xvOBAAXmLeHyHVK28LozSAaJ9okQgD25wq5Ktk95oMTmPIC+rH66KcK6371ivNk8g==
"@material/toolbar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/toolbar/-/toolbar-0.44.0.tgz#6689aecdeccc78b7a890a3abbe8b68a2c6339307"
integrity sha512-YgLlOFQ5VzFLQBpXYSMviEbYox0fia+sasHuYPUhTAtas1ExVt9EEiIolDSVvhv2PruTReKKefxSbXAqGlOHog==
dependencies:
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/top-app-bar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/top-app-bar/-/top-app-bar-0.44.0.tgz#2495c7f9567568fb961ccced24f479c4806a72af"
integrity sha512-tf0yXQJARYs8UPaH8oo3LnsSHEiur7Zm8Fc3hv3F0gNRRaZYBjwsMQMVbZZaWoQCWskMALyntBg+Fo18zdgDxw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/typography@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/typography/-/typography-0.44.0.tgz#cf61dce2ee89bfa084d86e1b0f270a585bf9dfaf"
integrity sha512-m4SjA9OjZRDKowN3cPzEa8e2GlTlEn3ZmW/Fy9eRNSp83iY+8a0xl6kCaF80v5qAVwVcpfEFyEHWxMJtkBw2uA==
"@material/typography@^0.44.1":
version "0.44.1"
resolved "https://registry.yarnpkg.com/@material/typography/-/typography-0.44.1.tgz#a94f01172f9122180bc2ce0aa55658183a35590d"
integrity sha512-wMXHusg+Lp5Fdgoj3m0c+Lt6GCeGSh3EPRtQ1TQ2bwdBa0et2FqBaQRgXoq3tVmr0O/7unTfa0DoXlh4nVp1wA==
dependencies:
"@material/feature-targeting" "^0.44.1"
focus-trap@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-4.0.2.tgz#4ee2b96547c9ea0e4252a2d4b2cca68944194663"
integrity sha512-HtLjfAK7Hp2qbBtLS6wEznID1mPT+48ZnP2nkHzgjpL4kroYHg0CdqJ5cTXk+UO5znAxF5fRUkhdyfgrhh8Lzw==
dependencies:
tabbable "^3.1.2"
xtend "^4.0.1"
material-components-web@^0.44.0:
version "0.44.0"
resolved "https://registry.yarnpkg.com/material-components-web/-/material-components-web-0.44.0.tgz#ff782e8d7bdd8212d3c6022a731258d0d42da531"
integrity sha512-BSRLf58SMVhAvlDhJDlcgYuvzeMwbMHKTJ7oIB8LaM24ZpXBxP9XCYJpKheMtiVLrgllCGDlJ/47OIDReHQXdQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/auto-init" "^0.41.0"
"@material/base" "^0.41.0"
"@material/button" "^0.44.0"
"@material/card" "^0.44.0"
"@material/checkbox" "^0.44.0"
"@material/chips" "^0.44.0"
"@material/dialog" "^0.44.0"
"@material/dom" "^0.41.0"
"@material/drawer" "^0.44.0"
"@material/elevation" "^0.44.0"
"@material/fab" "^0.44.0"
"@material/feature-targeting" "^0.44.0"
"@material/floating-label" "^0.44.0"
"@material/form-field" "^0.44.0"
"@material/grid-list" "^0.44.0"
"@material/icon-button" "^0.44.0"
"@material/icon-toggle" "^0.44.0"
"@material/image-list" "^0.44.0"
"@material/layout-grid" "^0.41.0"
"@material/line-ripple" "^0.43.0"
"@material/linear-progress" "^0.43.0"
"@material/list" "^0.44.0"
"@material/menu" "^0.44.0"
"@material/menu-surface" "^0.44.0"
"@material/notched-outline" "^0.44.0"
"@material/radio" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/select" "^0.44.0"
"@material/selection-control" "^0.44.0"
"@material/shape" "^0.43.0"
"@material/slider" "^0.44.0"
"@material/snackbar" "^0.44.0"
"@material/switch" "^0.44.0"
"@material/tab" "^0.44.0"
"@material/tab-bar" "^0.44.0"
"@material/tab-indicator" "^0.43.0"
"@material/tab-scroller" "^0.44.0"
"@material/textfield" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/toolbar" "^0.44.0"
"@material/top-app-bar" "^0.44.0"
"@material/typography" "^0.44.0"
tabbable@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-3.1.2.tgz#f2d16cccd01f400e38635c7181adfe0ad965a4a2"
integrity sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ==
xtend@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

View File

@ -1,42 +0,0 @@
{
"manifest_version": 2,
"name": "Coder",
"version": "1",
"icons": {
"128": "icon_128.png"
},
"permissions": [
"storage",
"webview",
"http://*/*",
"https://*/*"
],
"app": {
"background": {
"scripts": [
"out/background.js"
]
},
"content": {
"scripts": [
"out/content.js"
]
}
},
"commands": {
"toggle-feature-foo": {
"suggested_key": {
"default": "Ctrl+W"
},
"description": "Toggle feature foo",
"global": true
}
},
"sockets": {
"tcpServer": {
"listen": [
""
]
}
}
}

View File

@ -1,9 +0,0 @@
{
"name": "@coder/chrome-app",
"dependencies": {
"@types/chrome": "^0.0.79"
},
"scripts": {
"build": "../../../node_modules/.bin/webpack --config ./webpack.config.js"
}
}

View File

@ -1,13 +0,0 @@
/// <reference path="../node_modules/@types/chrome/index.d.ts" />
// tslint:disable-next-line:no-any
const chromeApp = (<any>chrome).app;
chromeApp.runtime.onLaunched.addListener(() => {
chromeApp.window.create("src/index.html", {
outerBounds: {
width: 400,
height: 500,
},
});
});

View File

@ -1,92 +0,0 @@
//@ts-ignore
import { TcpHost, TcpServer, TcpConnection } from "@coder/app/common/src/app";
import { Event, Emitter } from "@coder/events/src";
export const tcpHost: TcpHost = {
listen(host: string, port: number): Promise<TcpServer> {
const socketApi: {
readonly tcpServer: {
create(props: {}, cb: (createInfo: { readonly socketId: number }) => void): void;
listen(socketId: number, address: string, port: number, callback: (result: number) => void): void;
disconnect(socketId: number, callback: () => void): void;
readonly onAccept: {
addListener(callback: (info: { readonly socketId: number; readonly clientSocketId: number }) => void): void;
};
};
readonly tcp: {
readonly onReceive: {
addListener(callback: (info: { readonly socketId: number; readonly data: ArrayBuffer; }) => void): void;
};
close(socketId: number, callback?: () => void): void;
send(socketId: number, data: ArrayBuffer, callback?: () => void): void;
setPaused(socketId: number, value: boolean): void;
};
// tslint:disable-next-line:no-any
} = (<any>chrome).sockets;
return new Promise((resolve, reject): void => {
socketApi.tcpServer.create({}, (createInfo) => {
const serverSocketId = createInfo.socketId;
socketApi.tcpServer.listen(serverSocketId, host, port, (result) => {
if (result < 0) {
return reject("Failed to listen: " + chrome.runtime.lastError);
}
const connectionEmitter = new Emitter<TcpConnection>();
socketApi.tcpServer.onAccept.addListener((info) => {
if (info.socketId !== serverSocketId) {
return;
}
const dataEmitter = new Emitter<ArrayBuffer>();
socketApi.tcp.onReceive.addListener((recvInfo) => {
if (recvInfo.socketId !== info.clientSocketId) {
return;
}
dataEmitter.emit(recvInfo.data);
});
socketApi.tcp.setPaused(info.clientSocketId, false);
connectionEmitter.emit({
send: (data): Promise<void> => {
return new Promise<void>((res): void => {
socketApi.tcp.send(info.clientSocketId, data, () => {
res();
});
});
},
close: (): Promise<void> => {
return new Promise((res): void => {
socketApi.tcp.close(info.clientSocketId, () => {
res();
});
});
},
get onData(): Event<ArrayBuffer> {
return dataEmitter.event;
},
});
});
resolve({
get onConnection(): Event<TcpConnection> {
return connectionEmitter.event;
},
close: (): Promise<void> => {
return new Promise((res): void => {
socketApi.tcpServer.disconnect(serverSocketId, () => {
res();
});
});
},
});
});
});
});
},
};

View File

@ -1,33 +0,0 @@
import { create } from "@coder/app/common/src/app";
import { tcpHost } from "./chome";
create({
storage: {
get: <T>(key: string): Promise<T | undefined> => {
return new Promise<T | undefined>((resolve, reject): void => {
try {
chrome.storage.sync.get(key, (items) => {
resolve(items[key]);
});
} catch (ex) {
reject(ex);
}
});
},
set: <T>(key: string, value: T): Promise<void> => {
return new Promise<void>((resolve, reject): void => {
try {
chrome.storage.sync.set({
[key]: value,
}, () => {
resolve();
});
} catch (ex) {
reject(ex);
}
});
},
},
tcp: tcpHost,
node: document.getElementById("main") as HTMLDivElement,
});

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="style-src 'self' https://use.typekit.net; font-src 'self' https://use.typekit.net;">
<link rel="stylesheet" type="text/css" href="/out/main.css">
</head>
<body>
<div id="main"></div>
<script src="/out/content.js"></script>
</body>
</html>

View File

@ -1,37 +0,0 @@
const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const HtmlWebpackPlugin = require("html-webpack-plugin");
const prod = process.env.NODE_ENV === "production";
module.exports = [
merge(require(path.join(__dirname, "../../../scripts", "webpack.general.config.js"))(), {
devtool: "none",
mode: "development",
target: "web",
output: {
path: path.join(__dirname, "out"),
filename: "background.js",
},
entry: [
"./packages/app/chrome/src/background.ts"
],
plugins: [
]
}),
merge(require(path.join(__dirname, "../../../scripts", "webpack.general.config.js"))(), {
devtool: "none",
mode: "development",
target: "web",
output: {
path: path.join(__dirname, "out"),
filename: "content.js",
},
entry: [
"./packages/app/chrome/src/content.ts"
],
plugins: [
]
}),
];

View File

@ -1,22 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/chrome@^0.0.79":
version "0.0.79"
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.79.tgz#1c83b35bd9b21b6204fb56e4816a1ea65dc013e5"
integrity sha512-4+Xducpig6lpwVX65Hk8KSZwRoURHXMDbd38SDNcV8TBaw4xyJki39fjB1io2h7ip+BsyFvgTm9OxR5qneLPiA==
dependencies:
"@types/filesystem" "*"
"@types/filesystem@*":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.29.tgz#ee3748eb5be140dcf980c3bd35f11aec5f7a3748"
integrity sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==
dependencies:
"@types/filewriter" "*"
"@types/filewriter@*":
version "0.0.28"
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.28.tgz#c054e8af4d9dd75db4e63abc76f885168714d4b3"
integrity sha1-wFTor02d11205jq8dviFFocU1LM=

View File

@ -1,13 +0,0 @@
{
"name": "@coder/app-common",
"main": "src/app.ts",
"dependencies": {
"material-components-web": "^0.44.0",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},
"devDependencies": {
"@types/react": "^16.8.2",
"@types/react-dom": "^16.8.0"
}
}

View File

@ -1,279 +0,0 @@
@font-face {
font-family: 'aktiv-grotesk';
font-weight: 400;
// src: url("fonts/AktivGroteskRegular.ttf"); /* IE9 Compat Modes */
src: url("fonts/AktivGroteskRegular.woff2") format("woff2"), url("fonts/AktivGroteskRegular.woff") format("woff"); /* Pretty Modern Browsers */
}
@font-face {
font-family: 'aktiv-grotesk';
font-weight: 500;
src: url("fonts/AktivGroteskMedium.woff2") format("woff2"), url("fonts/AktivGroteskMedium.woff") format("woff"); /* Pretty Modern Browsers */
// src: url("fonts/AktivGroteskMedium.ttf");
}
@font-face {
font-family: 'aktiv-grotesk';
font-weight: 700;
src: url("fonts/AktivGroteskBold.woff2") format("woff2"), url("fonts/AktivGroteskBold.woff") format("woff"); /* Pretty Modern Browsers */
// src: url("fonts/AktivGroteskBold.ttf") format("ttf"); /* IE9 Compat Modes */
}
body, button, input {
font-family: 'aktiv-grotesk',sans-serif !important;
}
body {
margin: 0;
background-color: #F6F8FB;
--mdc-theme-primary: #2A2E37;
}
webview {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
opacity: 0;
pointer-events: none;
transition: 150ms opacity ease;
&.active {
opacity: 1;
pointer-events: all;
}
}
.logo-fill {
fill: #2A2E37;
}
.main {
& > .header {
width: 100%;
height: 71px;
border-bottom: 1px solid rgba(117, 122, 131, 0.1);
display: flex;
margin-bottom: 60px;
.logo {
max-height: fit-content;
width: 145px;
}
.shrinker {
max-width: 1145px;
width: 100%;
margin: 0 auto;
display: flex;
}
}
.content {
max-width: 960px;
width: 100%;
padding-bottom: 100px;
margin: 0 auto;
}
}
.servers {
color: #2B343B;
& > .header {
display: flex;
flex-direction: row;
align-items: center;
padding-bottom: 21px;
h3 {
font-size: 24px;
font-weight: 500;
letter-spacing: 0.35px;
line-height: 33px;
margin: 0;
margin-left: 30px;
}
.add-server {
margin-left: auto;
border-radius: 24px;
font-weight: bold;
font-size: 14px;
letter-spacing: 1.25px;
}
.refresh {
margin-left: 16px;
margin-right: 15px;
cursor: pointer;
svg {
@keyframes rotate {
100% { transform: rotate(360deg); }
}
&.refreshing {
animation: rotate 1s linear infinite;
}
}
}
}
& > .grid {
display: grid;
grid-template-columns: 1fr 1.6fr 1.3fr 1.1fr 0.6fr 0.4fr;
box-shadow: 0 18px 80px 10px rgba(69, 65, 78, 0.08);
border-radius: 0 0 5px 5px;
.mdc-linear-progress {
grid-column-start: 1;
grid-column-end: 7;
// height: 0;
position: relative;
--mdc-theme-primary: rgb(107, 109, 102);
height: 5px;
&:after {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: #2A2E37;
transition: 500ms opacity ease;
content: " ";
}
&.loading {
&:after {
opacity: 0;
}
}
}
.title, .value {
padding-top: 14px;
padding-bottom: 14px;
}
.title {
background-color: var(--mdc-theme-primary);
font-size: 10px;
color: #9D9FA4;
font-weight: bold;
letter-spacing: 2px;
line-height: 12px;
text-transform: uppercase;
// padding-top: 15px;
// padding-bottom: 10px;
&:first-child {
padding-left: 30px;
border-radius: 10px 0 0 0;
}
&:nth-child(6) {
padding-right: 30px;
border-radius: 0 10px 0 0;
}
&.servername {
color: white;
}
}
.value {
border-top: 1px solid #EBEBF2;
font-size: 14px;
letter-spacing: 0.2px;
display: flex;
align-items: center;
color: #717680;
background-color: white;
&.dark {
background-color: #F6F8FB;
}
&.servername {
.logo {
height: 25px;
}
}
&.strong {
font-weight: 600;
color: #2B343B;
font-size: 14px;
letter-spacing: 0.6px;
}
&.status {
padding-left: 36px;
span {
margin-left: 7px;
line-height: 0px;
}
}
&.buttons {
button {
margin-left: auto;
border-radius: 24px;
border: 1px solid #CFD1D7;
font-size: 14px;
font-weight: bold;
letter-spacing: 1.25px;
line-height: 16px;
padding-left: 18px;
padding-right: 18px;
}
}
&.icons {
padding-left: 16px;
}
&:last-child {
border-bottom-right-radius: 5px;
}
&:nth-last-child(6) {
border-bottom-left-radius: 5px;
}
}
}
}
.flex-row {
display: flex;
flex-direction: row;
}
.floater {
box-shadow: 0 8px 80px 10px rgba(69, 65, 78, 0.08);
border-radius: 10px;
padding: 3em;
min-width: 300px;
width: 100%;
& > h1 {
font-size: 3.5em;
margin-top: 0px;
// margin-bottom: 0px;
}
}
.mdc-ripple-upgraded--unbounded {
padding: 2px;
padding-top: 5px;
cursor: pointer;
}

View File

@ -1,33 +0,0 @@
//@ts-ignore
import { MDCTextField } from "@material/textfield";
import { TcpHost } from "./connection";
import { StorageProvider } from "./storage";
import "material-components-web/dist/material-components-web.css";
import "./app.scss";
import "./tooltip.scss";
import * as React from "react";
import { render } from "react-dom";
import { Main } from "./containers";
export * from "./connection";
export interface App {
readonly tcp: TcpHost;
readonly storage: StorageProvider;
readonly node: HTMLElement;
}
export interface RegisteredServer {
readonly host: "coder" | "self";
readonly hostname: string;
readonly name: string;
}
export const create = async (app: App): Promise<void> => {
let servers = await app.storage.get<RegisteredServer[]>("servers");
if (!servers) {
servers = [];
}
render(<Main />, app.node);
};

View File

@ -1,17 +0,0 @@
import { Event } from "@coder/events";
import { TunnelCloseEvent } from "@coder/tunnel/src/client";
export interface TcpHost {
listen(host: string, port: number): Promise<TcpServer>;
}
export interface TcpServer {
readonly onConnection: Event<TcpConnection>;
close(): Promise<void>;
}
export interface TcpConnection {
readonly onData: Event<ArrayBuffer>;
send(data: ArrayBuffer): Promise<void>;
close(): Promise<void>;
}

View File

@ -1,573 +0,0 @@
//@ts-ignore
import { MDCRipple } from "@material/ripple";
//@ts-ignore
import { MDCTextField } from "@material/textfield";
//@ts-ignore
import { MDCLinearProgress } from "@material/linear-progress";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { RegisteredServer } from "./app";
// tslint:disable-next-line:no-any
declare var WebSettings: any;
interface AuthedUser {
readonly username: string;
}
export class Main extends React.Component<void, {
readonly view: "servers" | "add-server";
readonly loading: boolean;
}> {
private webview: HTMLWebViewElement | undefined;
public constructor(props: void) {
super(props);
this.state = {
view: "servers",
loading: false,
};
}
public componentDidMount(): void {
window.addEventListener("message", (event) => {
if (event.data === "back") {
if (this.webview) {
this.webview.classList.remove("active");
}
}
if (event.data === "loaded") {
if (this.webview) {
// this.setState({ loading: false });
// this.webview.classList.add("active");
}
}
});
if (this.webview) {
this.webview.addEventListener("error", (event) => {
console.error(event);
});
this.webview.addEventListener("loadstart", (event) => {
this.setState({ loading: true });
});
this.webview.addEventListener("loadstop", (event) => {
this.setState({ loading: false });
this.webview!.classList.add("active");
// tslint:disable-next-line:no-any
const cw = (this.webview as any).contentWindow as Window;
cw.postMessage("app", "*");
});
}
}
public render(): JSX.Element {
return (
<div className="main">
<div className="header">
<div className="shrinker">
<Logo />
</div>
</div>
<div className="content">
{((): JSX.Element => {
switch (this.state.view) {
case "servers":
return (
<Servers servers={[
{
host: "coder",
hostname: "--",
name: "Coder",
},
{
host: "self",
hostname: "http://localhost:8080",
name: "Dev Server",
},
]}
user={{
username: "Kyle",
}}
onSelect={(server): void => {
if (this.webview) {
this.webview.setAttribute("src", server.hostname);
}
}}
onAddServer={() => this.setState({ view: "add-server" })}
loading={this.state.loading}
/>
);
case "add-server":
return (
<div>Add server</div>
);
}
})()}
</div>
<webview ref={(wv: HTMLWebViewElement): HTMLWebViewElement => this.webview = wv}></webview>
</div>
);
}
}
export class AddServer extends React.Component {
public render(): JSX.Element {
return (
<div className="add-server">
<h3>Add Server</h3>
<p>Something about what you can do once you add your own server. A link to setup guides for this would be great as well.</p>
<Input label="Address" id="address" />
<br></br>
</div>
);
}
}
export class Servers extends React.Component<{
readonly user?: AuthedUser;
readonly servers: ReadonlyArray<RegisteredServer>;
readonly onSelect: (server: RegisteredServer) => void;
readonly onAddServer: () => void;
readonly loading: boolean;
}, {
readonly refreshing: boolean;
}> {
// tslint:disable-next-line:no-any
public constructor(props: any) {
super(props);
this.state = {
refreshing: false,
};
}
public render(): JSX.Element {
return (
<div className="servers">
<div className="header">
<h3>Servers</h3>
<Button onClick={(): void => this.props.onAddServer()} className="add-server" type="unelevated">Add Server</Button>
<Ripple>
<div className="refresh">
<svg onClick={(): void => this.doRefresh()} className={this.state.refreshing ? "refreshing" : ""} width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g>
<g transform="translate(4.000000, 4.000000)" fill="#2A2E37">
<path d="M8,3 C9.179,3 10.311,3.423 11.205,4.17 L8.883,6.492 L15.094,7.031 L14.555,0.82 L12.625,2.75 C11.353,1.632 9.71,1 8,1 C4.567,1 1.664,3.454 1.097,6.834 L3.07,7.165 C3.474,4.752 5.548,3 8,3 Z" id="Path"></path>
<path d="M8,13 C6.821,13 5.689,12.577 4.795,11.83 L7.117,9.508 L0.906,8.969 L1.445,15.18 L3.375,13.25 C4.647,14.368 6.29,15 8,15 C11.433,15 14.336,12.546 14.903,9.166 L12.93,8.835 C12.526,11.248 10.452,13 8,13 Z" id="Path"></path>
</g>
<rect id="Rectangle" fillRule="nonzero" x="0" y="0" width="24" height="24"></rect>
</g>
</g>
</svg>
</div>
</Ripple>
</div>
<div className="grid">
<div className="title status">
Status
</div>
<div className="title servername">
Server Name
</div>
<div className="title hostname">
Hostname
</div>
<div className="title details">
Details
</div>
<div className="title">
{/* Used for continue/launch buttons */}
</div>
<div className="title">
{/* Used for logout and delete buttons */}
</div>
<div role="progressbar" className={`mdc-linear-progress mdc-linear-progress--indeterminate ${this.props.loading ? "loading" : ""}`} ref={(d) => {
if (d) new MDCLinearProgress(d)}}>
<div className="mdc-linear-progress__buffering-dots"></div>
<div className="mdc-linear-progress__buffer"></div>
<div className="mdc-linear-progress__bar mdc-linear-progress__primary-bar">
<span className="mdc-linear-progress__bar-inner"></span>
</div>
<div className="mdc-linear-progress__bar mdc-linear-progress__secondary-bar">
<span className="mdc-linear-progress__bar-inner"></span>
</div>
</div>
{this.props.servers.map((server, i) => {
return (
<Server key={server.hostname + i} user={this.props.user} server={server} onSelect={(): void => this.props.onSelect(server)} />
);
})}
</div>
</div>
);
}
private doRefresh(): void {
if (this.state.refreshing) {
return;
}
this.setState({
refreshing: true,
}, () => {
setTimeout(() => {
this.setState({
refreshing: false,
});
}, 1500);
});
}
}
interface ServerProps {
readonly user?: AuthedUser;
readonly server: RegisteredServer;
readonly onSelect: () => void;
}
export class Server extends React.Component<ServerProps, {
readonly user?: AuthedUser;
readonly status: "Online" | "Offline" | "Checking";
readonly version: string;
}> {
// tslint:disable-next-line:no-any
public constructor(props: ServerProps) {
super(props);
this.state = {
status: props.server.host === "coder" ? "Online" : "Checking",
version: "",
};
}
public componentWillMount(): void {
if (this.props.server.host !== "self") {
return;
}
const xhr = new XMLHttpRequest();
xhr.open("GET", this.props.server.hostname);
xhr.addEventListener("error", (err) => {
this.setState({
status: "Offline",
});
});
xhr.addEventListener("loadend", () => {
if (xhr.status === 200) {
this.setState({
status: "Online",
version: process.env.VERSION,
});
} else {
this.setState({
status: "Offline",
});
}
});
xhr.send();
}
public render(): JSX.Element {
return (
<>
<div className={`status value ${this.extraClasses}`}>
{((): JSX.Element => {
switch (this.state.status) {
case "Offline":
return (
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<g id="Artboard-Copy-3" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<circle id="Oval" stroke="#2B343B" strokeWidth="1.5" fillRule="nonzero" cx="8" cy="8" r="7.25"></circle>
<path d="M5.15444712,5.15444712 L10.8455529,10.8455529" id="Path-4" stroke="#2B343B" strokeWidth="1.5" fillRule="nonzero"></path>
<path d="M5.15444712,5.15444712 L10.8455529,10.8455529" id="Path-4" stroke="#2B343B" strokeWidth="1.5" fillRule="nonzero" transform="translate(8.000000, 8.000000) scale(-1, 1) translate(-8.000000, -8.000000) "></path>
</g>
</svg>
);
case "Online":
return (
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<g id="Artboard-Copy-4" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="checkmark-copy-21" fillRule="nonzero">
<circle id="Oval" fill="#2B343B" cx="8" cy="8" r="8"></circle>
<polyline id="Path-2" stroke="#FFFFFF" strokeWidth="1.5" points="3.46296296 8.62222222 6.05555556 11.1111111 12.537037 4.88888889"></polyline>
</g>
</g>
</svg>
);
case "Checking":
return (
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<g id="Artboard-Copy-5" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<circle id="Oval" stroke="#2B343B" strokeWidth="1.5" fillRule="nonzero" cx="8" cy="8" r="7.25"></circle>
<polyline id="Path" stroke="#2B343B" strokeWidth="1.5" points="7.90558664 4.63916767 7.90558664 8.63916767 11.9055866 8.63916767"></polyline>
</g>
</svg>
);
default:
throw new Error("unsupported status");
}
})()}
<span>
{this.state.status}
</span>
</div>
<div className={`servername value strong ${this.extraClasses}`}>
{this.props.server.host === "coder" ? (
<Logo />
) : this.props.server.name}
</div>
<div className={`hostname value ${this.extraClasses}`}>
{this.props.server.hostname}
</div>
<div className={`details value ${this.extraClasses}`}>
{this.props.server.host === "coder" && this.props.user ? `Logged in as ${this.props.user.username}` : this.state.version}
</div>
<div className={`buttons value ${this.extraClasses}`}>
<Button onClick={(): void => this.props.onSelect()} className="add-server" type="outlined">{this.props.server.host === "coder" ? "Continue" : "Launch"}</Button>
</div>
<div className={`icons value ${this.extraClasses}`}>
<Ripple>
<div>
{this.props.server.host === "coder" ? (
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<g id="Artboard" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="log-out-copy-2" transform="translate(4.000000, 4.000000)" fill="#2A2E37">
<polygon id="Path" points="4 4 0 8 4 12 4 9 10 9 10 7 4 7"></polygon>
<path d="M15,16 L6,16 C5.4,16 5,15.6 5,15 L5,12 L7,12 L7,14 L14,14 L14,2 L7,2 L7,4 L5,4 L5,1 C5,0.4 5.4,0 6,0 L15,0 C15.6,0 16,0.4 16,1 L16,15 C16,15.6 15.6,16 15,16 Z" id="Path"></path>
</g>
</g>
</svg>
) : (
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
<g id="Artboard" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="bin" transform="translate(4.000000, 4.000000)" fill="#2B343B">
<rect id="Rectangle" x="5" y="7" width="2" height="6"></rect>
<rect id="Rectangle" x="9" y="7" width="2" height="6"></rect>
<path d="M12,1 C12,0.4 11.6,0 11,0 L5,0 C4.4,0 4,0.4 4,1 L4,3 L0,3 L0,5 L1,5 L1,15 C1,15.6 1.4,16 2,16 L14,16 C14.6,16 15,15.6 15,15 L15,5 L16,5 L16,3 L12,3 L12,1 Z M6,2 L10,2 L10,3 L6,3 L6,2 Z M13,5 L13,14 L3,14 L3,5 L13,5 Z" id="Shape" fillRule="nonzero"></path>
</g>
</g>
</svg>
)}
</div>
</Ripple>
</div>
</>
);
}
private get extraClasses(): string {
return this.props.server.host === "coder" ? "dark" : "";
}
}
export class Input extends React.Component<{
readonly label: string;
readonly id: string;
readonly type?: string;
}> {
private wrapper: HTMLDivElement | undefined;
public componentDidMount(): void {
if (this.wrapper) {
const textInput = new MDCTextField(this.wrapper);
}
}
public render(): JSX.Element {
return (
<div className="mdc-text-field mdc-text-field--outlined" ref={(i: HTMLDivElement): HTMLDivElement => this.wrapper = i}>
<input type={this.props.type || "text"} id={this.props.id} className="mdc-text-field__input" spellCheck={false}></input>
<div className="mdc-notched-outline">
<div className="mdc-notched-outline__leading"></div>
<div className="mdc-notched-outline__notch">
<label htmlFor={this.props.id} className="mdc-floating-label">{this.props.label}</label>
</div>
<div className="mdc-notched-outline__trailing"></div>
</div>
</div>
);
}
}
export class Button extends React.Component<{
readonly type: "outlined" | "unelevated";
readonly className?: string;
readonly onClick?: () => void;
}> {
private button: HTMLButtonElement | undefined;
public componentDidMount(): void {
if (this.button) {
const b = new MDCRipple(this.button);
}
}
public render(): JSX.Element {
return (
<button onClick={() => this.props.onClick ? this.props.onClick() : undefined} className={`mdc-button mdc-button--${this.props.type} ${this.props.className || ""}`} ref={(b: HTMLButtonElement): HTMLButtonElement => this.button = b}>
<span className="mdc-button__label">{this.props.children}</span>
</button>
);
}
}
export class Tooltip extends React.Component<{
readonly message: string;
}> {
public componentDidMount(): void {
Object.keys(this.refs).forEach((ref) => {
const el = this.refs[ref];
if (el) {
const element = ReactDOM.findDOMNode(el);
if (element) {
const te = document.createElement("div");
te.className = "md-tooltip-content";
te.innerHTML = this.props.message;
element.appendChild(te);
(element as HTMLElement).classList.add("md-tooltip");
}
}
});
}
public render(): JSX.Element {
return (
<>
{React.Children.map(this.props.children, (element, idx) => {
return React.cloneElement(element as any, { ref: idx });
})}
</>
);
}
}
export class Ripple extends React.Component<{
readonly className?: string;
}> {
public componentDidMount(): void {
Object.keys(this.refs).forEach((ref) => {
const el = this.refs[ref];
if (el) {
const element = ReactDOM.findDOMNode(el);
if (element) {
(element as HTMLElement).classList.add("mdc-ripple-surface");
(element as HTMLElement).setAttribute("data-mdc-ripple-is-unbounded", "");
const r = new MDCRipple(element);
}
}
});
}
public render(): JSX.Element {
return (
<>
{React.Children.map(this.props.children, (element, idx) => {
return React.cloneElement(element as any, { ref: idx });
})}
</>
);
}
}
export class Logo extends React.Component {
public render(): JSX.Element {
return (
<svg className="logo" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 471 117"
style={{enableBackground: "new 0 0 471 117"} as any} xmlSpace="preserve">
<g>
<g>
<path className="logo-fill" d="M217,75.6c5.9,0,10.7-2.3,14.5-7l7.7,7.9c-6.1,6.9-13.3,10.3-21.6,10.3s-15.1-2.6-20.5-7.9
C191.7,73.7,189,67,189,59s2.7-14.7,8.2-20s12.2-8,20.1-8c8.8,0,16.2,3.4,22.2,10.1l-7.5,8.5c-3.8-4.7-8.5-7.1-14.2-7.1
c-4.5,0-8.4,1.5-11.6,4.4c-3.2,3-4.8,6.9-4.8,11.9s1.5,9,4.5,12.1C209,74.1,212.6,75.6,217,75.6z M284.1,46.7
c-3.1-3.4-6.9-5.1-11.4-5.1s-8.3,1.7-11.4,5.1s-4.6,7.5-4.6,12.3s1.5,8.9,4.6,12.3s6.9,5,11.4,5s8.3-1.7,11.4-5
c3.1-3.4,4.6-7.5,4.6-12.3S287.2,50.1,284.1,46.7z M272.7,86.8c-8,0-14.7-2.7-20.1-8s-8.2-11.9-8.2-19.9c0-7.9,2.7-14.5,8.2-19.9
c5.4-5.3,12.2-8,20.1-8c8,0,14.7,2.7,20.1,8s8.2,11.9,8.2,19.9c0,7.9-2.7,14.5-8.2,19.9C287.4,84.1,280.7,86.8,272.7,86.8z
M352.3,39.4c5.1,4.7,7.7,11.2,7.7,19.6s-2.5,15-7.5,19.9s-12.7,7.3-22.9,7.3h-18.4V32.3h19C339.8,32.4,347.2,34.7,352.3,39.4z
M343.5,71.5c3-2.8,4.4-6.8,4.4-12.1s-1.5-9.4-4.4-12.2c-3-2.9-7.5-4.3-13.6-4.3h-6.7v32.8h7.6C336.3,75.6,340.5,74.2,343.5,71.5z
M409.3,32.4v10.7h-26.8v11.1h24.1v10.3h-24.1v11.2h27.7v10.6h-39.7V32.4H409.3L409.3,32.4z M464.6,50.3c0,8.6-3.4,14.2-10.3,16.7
l13.6,19.3h-14.8l-11.9-17.2h-8.3v17.2h-12V32.4h20.4c8.4,0,14.4,1.4,17.9,4.2C462.8,39.4,464.6,44,464.6,50.3z M450.1,56.7
c1.5-1.3,2.2-3.5,2.2-6.4s-0.8-4.9-2.3-6s-4.2-1.6-8.1-1.6h-9v16h8.8C445.8,58.7,448.6,58,450.1,56.7z"/>
</g>
<g>
<path className="logo-fill" d="M164.8,50.9c-3.3,0-5.5-1.9-5.5-5.8V22.7c0-14.3-6-22.2-21.5-22.2h-7.2v15.1h2.2c6.1,0,9,3.3,9,9.2v19.8
c0,8.6,2.6,12.1,8.3,13.9c-5.7,1.7-8.3,5.3-8.3,13.9c0,4.9,0,9.8,0,14.7c0,4.1,0,8.1-1.1,12.2c-1.1,3.8-2.9,7.4-5.4,10.5
c-1.4,1.8-3,3.3-4.8,4.7v2h7.2c15.5,0,21.5-7.9,21.5-22.2V71.9c0-4,2.1-5.8,5.5-5.8h4.1V51h-4V50.9L164.8,50.9z"/>
<path className="logo-fill" d="M115.8,23.3H93.6c-0.5,0-0.9-0.4-0.9-0.9v-1.7c0-0.5,0.4-0.9,0.9-0.9h22.3c0.5,0,0.9,0.4,0.9,0.9v1.7
C116.8,22.9,116.3,23.3,115.8,23.3z"/>
<path className="logo-fill" d="M119.6,44.9h-16.2c-0.5,0-0.9-0.4-0.9-0.9v-1.7c0-0.5,0.4-0.9,0.9-0.9h16.2c0.5,0,0.9,0.4,0.9,0.9V44
C120.5,44.4,120.1,44.9,119.6,44.9z"/>
<path className="logo-fill" d="M126,34.1H93.6c-0.5,0-0.9-0.4-0.9-0.9v-1.7c0-0.5,0.4-0.9,0.9-0.9h32.3c0.5,0,0.9,0.4,0.9,0.9v1.7
C126.8,33.6,126.5,34.1,126,34.1z"/>
<g>
<path className="logo-fill" d="M67.9,28.2c2.2,0,4.4,0.2,6.5,0.7v-4.1c0-5.8,3-9.2,9-9.2h2.2V0.5h-7.2c-15.5,0-21.5,7.9-21.5,22.2v7.4
C60.4,28.9,64.1,28.2,67.9,28.2z"/>
</g>
<path className="logo-fill" d="M132.8,82.6c-1.6-12.7-11.4-23.3-24-25.7c-3.5-0.7-7-0.8-10.4-0.2c-0.1,0-0.1-0.1-0.2-0.1
c-5.5-11.5-17.3-19.1-30.1-19.1S43.6,44.9,38,56.4c-0.1,0-0.1,0.1-0.2,0.1c-3.6-0.4-7.2-0.2-10.8,0.7c-12.4,3-21.8,13.4-23.5,26
c-0.2,1.3-0.3,2.6-0.3,3.8c0,3.8,2.6,7.3,6.4,7.8c4.7,0.7,8.8-2.9,8.7-7.5c0-0.7,0-1.5,0.1-2.2c0.8-6.4,5.7-11.8,12.1-13.3
c2-0.5,4-0.6,5.9-0.3c6.1,0.8,12.1-2.3,14.7-7.7c1.9-4,4.9-7.5,8.9-9.4c4.4-2.1,9.4-2.4,14-0.8c4.8,1.7,8.4,5.3,10.6,9.8
c2.3,4.4,3.4,7.5,8.3,8.1c2,0.3,7.6,0.2,9.7,0.1c4.1,0,8.2,1.4,11.1,4.3c1.9,2,3.3,4.5,3.9,7.3c0.9,4.5-0.2,9-2.9,12.4
c-1.9,2.4-4.5,4.2-7.4,5c-1.4,0.4-2.8,0.5-4.2,0.5c-0.8,0-1.9,0-3.2,0c-4,0-12.5,0-18.9,0c-4.4,0-7.9-3.5-7.9-7.9V78.4V63.9
c0-1.2-1-2.2-2.2-2.2h-3.1c-6.1,0.1-11,6.9-11,14.1s0,26.3,0,26.3c0,7.8,6.3,14.1,14.1,14.1c0,0,34.7-0.1,35.2-0.1
c8-0.8,15.4-4.9,20.4-11.2C131.5,98.8,133.8,90.8,132.8,82.6z"/>
</g>
</g>
</svg>
);
}
}
// const $ = <K extends keyof HTMLElementTagNameMap>(tagName: K, className?: string, content?: string): HTMLElementTagNameMap[K] => {
// const el = document.createElement(tagName);
// if (className) {
// el.className = className;
// }
// if (content) {
// el.innerText = content;
// }
// return el;
// };
// const createInput = (id: string, labelName: string, type: string = "text"): HTMLDivElement => {
// // <div class="mdc-text-field mdc-text-field--outlined">
// // <input type="password" id="password" class="mdc-text-field__input">
// // <!-- <label class="mdc-floating-label" for="name">Name</label>
// // <div class="mdc-line-ripple"></div> -->
// // <div class="mdc-notched-outline">
// // <div class="mdc-notched-outline__leading"></div>
// // <div class="mdc-notched-outline__notch">
// // <label for="password" class="mdc-floating-label">Password</label>
// // </div>
// // <div class="mdc-notched-outline__trailing"></div>
// // </div>
// const wrapper = $("div", "mdc-text-field mdc-text-field--outlined");
// const input = $("input", "mdc-text-field__input");
// input.type = type;
// input.id = id;
// wrapper.appendChild(input);
// const notchedOutline = $("div", "mdc-notched-outline");
// notchedOutline.appendChild($("div", "mdc-notched-outline__leading"));
// const notch = $("div", "mdc-notched-outline__notch");
// const label = $("label", "mdc-floating-label", labelName);
// label.setAttribute("for", id);
// notch.appendChild(label);
// notchedOutline.appendChild(notch);
// wrapper.appendChild(notchedOutline);
// wrapper.appendChild($("div", "mdc-notched-outline__trailing"));
// const field = new MDCTextField(wrapper);
// return wrapper;
// };
// export const createCoderLogin = (parentNode: HTMLElement): void => {
// parentNode.appendChild($("h1", "header", "Login with Coder"));
// parentNode.appendChild(createInput("username", "Username"));
// parentNode.appendChild($("br"));
// parentNode.appendChild($("br"));
// parentNode.appendChild(createInput("password", "Password", "password"));
// };

View File

@ -1,5 +0,0 @@
export interface StorageProvider {
set<T>(key: string, value: T): Promise<void>;
get<T>(key: string): Promise<T | undefined>;
}

View File

@ -1,24 +0,0 @@
.md-tooltip {
position: relative;
}
.md-tooltip-content {
position: absolute;
bottom: -35px;
left: 50%;
padding: 7px;
transform: translateX(-50%) scale(0);
transition: transform 0.15s cubic-bezier(0, 0, 0.2, 1);
transform-origin: top;
background: rgba(67, 67, 67, 0.97);
color: white;
letter-spacing: 0.3px;
border-radius: 3px;
font-size: 12px;
font-weight: 500;
z-index: 2;
}
.md-tooltip:hover .md-tooltip-content {
transform: translateX(-50%) scale(1);
}

View File

@ -1,601 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@material/animation@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/animation/-/animation-0.41.0.tgz#315b45b32e1aeebee8a4cf555b8ad52076d09ddd"
integrity sha512-yYAwJbX3Q2AFd4dr6IYOsWLQy2HN8zWOFVl9AbUXunjzTfJCa/ecfXCriaT6qkmoNoHeTdJHRrsQJZC5GsPvzA==
"@material/auto-init@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/auto-init/-/auto-init-0.41.0.tgz#8a59bb0b83e0f51ead9508074f9a29b2b6a20eec"
integrity sha512-jp6L8MpYu7DudgDfA8iTyD9BwQrYPEDsIJGbqzN9vcCBl5FoBatkB8pcFXKr+1mRBk7T1Qmf6+H5nDtxyXjHEQ==
"@material/base@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/base/-/base-0.41.0.tgz#badadce711b4c25b1eb889a5e7581e32cd07c421"
integrity sha512-tEyzwBRu3d1H120SfKsDVYZHcqT5lKohh/7cWKR93aAaPDkSvjpKJIjyu2yuSkjpDduVZGzVocYbOvhUKhhzXQ==
"@material/button@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/button/-/button-0.44.0.tgz#f01dcbea88bdc314e7640b76e5558101c8b4d69d"
integrity sha512-T8u8s8rlB49D9/5Nh5b0XsKRgSq3X0yWGo71MgaTnCnwxt8oZ6PxW/cH6Nn3Xp0NCr3mlSVQs08BviUfAmtlsg==
dependencies:
"@material/elevation" "^0.44.0"
"@material/feature-targeting" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/card@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/card/-/card-0.44.0.tgz#e62050e3e77f525173a015119200055cd7b71bf0"
integrity sha512-fUixXuh133bVp5c1gPIHreL5jwMJEeVIQf0E4xdxhkA+i4ku8fIAvIW62EuCmfJsXicv4q8NG3Ip6pCY+NW3ZA==
dependencies:
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/checkbox@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-0.44.0.tgz#5d0eee1db006db9f0fb700bf1c20408292305cf7"
integrity sha512-IzucxG+NuPNyByGmHg/cuYJ5ooMKouuj994PZXZyqb7owfrjjtXm7wjav66cvCowbVbcoa1owQMGBi18C9f4TQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/chips@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/chips/-/chips-0.44.0.tgz#bf553a5bf5db7320978402ac92069c9688b84d5a"
integrity sha512-+qrme6sGwYmX/ixHAo3Z1M7lorsxRyKexn1l+BSBX5PBc2f4w5Ml1eYYYcyVGfLX9LXmefRk0G6dUXXPyCE00g==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/checkbox" "^0.44.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/dialog@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/dialog/-/dialog-0.44.0.tgz#388f93f9f225824c75cbe9da8c464a52d79972e8"
integrity sha512-V6ButfknOMKOscL0Y39yLjamxvrIuyugobjf5s44ZeJc+9jUSkC7M3zP+T7rh358NcX+JSPP8iCGmUn/+LXpMQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/dom" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
focus-trap "^4.0.2"
"@material/dom@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/dom/-/dom-0.41.0.tgz#6756865f97bad4c91ee75e69d769d7cdc25398ae"
integrity sha512-wOJrMwjPddYXpQFZAIaCLWI3TO/6KU1lxESTBzunni8A4FHQVWhokml5Xt85GqZwmPFeIF2s+D0wfbWyrGBuKQ==
"@material/drawer@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/drawer/-/drawer-0.44.0.tgz#74b3ddfb741bffc72331c7a73cf62716fd3f0ab3"
integrity sha512-AYwFe0jgqqSmJd1bny8JJTA2SScF86Wfbk99lXXEwd/acS8IbnnuH6zfAg6MyJX12FDb8dE8Z/Ok1IwLiVa9sQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/list" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
focus-trap "^4.0.2"
"@material/elevation@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-0.44.0.tgz#ca16a67188ce9810dc2fa3d7a39073e72df4b754"
integrity sha512-edNou34yFCSMb6XXe/6Y7AEh8DigWAhBUyIeMiMBD4k1km2xYCJbcnl8FBPJFteOrca97KoJComRlJPB6EurRQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/fab@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/fab/-/fab-0.44.0.tgz#0bcbbdfb6f24c53d59e08c9c0d400d2616dea184"
integrity sha512-1CEP4NlXDYioJ/YpSjh/MlIygtoC7CaHqIbucxX1O5WRPmS7K1uPt+o7netbLErAmcJdV/JrI/tqh9kKuX2x/Q==
dependencies:
"@material/animation" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/feature-targeting@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-0.44.0.tgz#52cc73f0c8a83159de0357aebe74f15f9856fb4c"
integrity sha512-ShuC2TOLfjFpYUCQFtvkqDJhM6HTaucSx5HkRbOvOG+VlpzDx6pAqRUmdVaq2p7tHoQf2vwPMlSVm3gOjWt4VQ==
"@material/floating-label@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/floating-label/-/floating-label-0.44.0.tgz#8694cd49f6905641b67a9e7a112b820d028f09c7"
integrity sha512-k4npGNxyMtnjgJZNjU5VvqqaUqlbzlbVAhepT8PxYTpj+4Skg6PjHwieTCDCgsbqHvFcQX+WfUrSZXY7wFV7cw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/form-field@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/form-field/-/form-field-0.44.0.tgz#b7518e885c0e953a2a5fe0140af927c30e066f4e"
integrity sha512-SK+V34dzoBCQ/CHn5nBp8BAh21Vj9p1pcok+/WpYBTeg4EphTYP2nUQLMNEN92l6zjgAYf+g9Ocj3t26HNHWqA==
dependencies:
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/grid-list@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/grid-list/-/grid-list-0.44.0.tgz#bd31d992ab1a910731e4a47c11fe91d44e3bc02b"
integrity sha512-NxLL0A48K1O14ZZymFIyf6HDbF33+NgXYXqP2yosTC3Jw4iwmUcJTpFTmSw1U/m1xT4zEpeKEGJ4vjVUWpS9Mg==
dependencies:
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/icon-button@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-0.44.0.tgz#febbcfd27d91eca8096ae042b9c07ed0f65345e9"
integrity sha512-n6L7RaRyEci6eGsuBTSEG+t9ATHAHaMlf9zuTWorEnIXY4DAmGO7ggBjw4+1XIOjhpLeIjyJdcvUK6Yz/UVM6Q==
dependencies:
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/icon-toggle@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/icon-toggle/-/icon-toggle-0.44.0.tgz#b9de32f194b5aa9721ca799d59be0f477a5c5305"
integrity sha512-8T1b4iK61/q/3U0iIjEDJ9do5viCQ45IbrQqa8EYCZ1KDU/Q8z5N+bvOzQK8XnTL51BdDRMgP9lfQZh6nszmkA==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/image-list@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/image-list/-/image-list-0.44.0.tgz#a27996962044ac8c9ce6cb509f63746f08ed2e98"
integrity sha512-kI9aKJdc1Bd02l8nRTGG1wy/lNkECScfnBmCiLQ3XjAFtRYd2eWO0Z/AVvUG3egsIZnZBxqFGGsf5Htm9E/HiQ==
dependencies:
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/layout-grid@^0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@material/layout-grid/-/layout-grid-0.41.0.tgz#2e7d3be76313e0684d573b10c2c6a88b3230d251"
integrity sha512-Sa5RNoTGgfIojqJ9E94p7/k11V6q/tGk7HwKi4AQNAPjxield0zcl3G/SbsSb8YSHoK+D+7OXDN+n11x6EqF7g==
"@material/line-ripple@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/line-ripple/-/line-ripple-0.43.0.tgz#6cb530bab53f055f3583646a21ad20c1703f3a83"
integrity sha512-sXZYW4Em5uLEnAuVsQCO+sVHsTg7J2TOTJ0+akwZFMmd2tmNicjarQdlGIE9iU7Wjm51NOoLAu6Mz+8kLg90bQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/linear-progress@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/linear-progress/-/linear-progress-0.43.0.tgz#4821424aa24c78de256e74a91d5be3df55c534d9"
integrity sha512-bqkDcob+xp1mFkyBsOkoaLgrtapmz7jznGoI3nmkqyk75EB2XQcn1H8Vr6cnp/jkF4nbKu0GdVJO3VXUFmGmrQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/list@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/list/-/list-0.44.0.tgz#cf1910e15b66759334b8618d1110fbcc72c3d326"
integrity sha512-35gkN1+XZaau9d9ngyN2x14bzkj/ajZCDm7mbWibDQy272A16j6KuFLQFA8RUQV04OgL4YPVxY87dpCn/p+uTg==
dependencies:
"@material/base" "^0.41.0"
"@material/dom" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/menu-surface@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/menu-surface/-/menu-surface-0.44.0.tgz#902c081df42859b925a5b4502791b3febf48f1ae"
integrity sha512-s49kvIlQ4H5wvMD4yeHMMqnamPod06IUagMK6Ry0oTpUANSnyeNXxa3HkScl7DMJiS8IJeV21fSLAzlZYP2PDQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/menu@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/menu/-/menu-0.44.0.tgz#776ec8a04406266a0a0a13eb140b1fd691e442cb"
integrity sha512-92XvAcv9rBW1jQ3UvwJ8zk9hbSRe/FqSuFdZ9fNPE348dCY2pbcdQfnUJTe3ycAN/I1c5frkrhx8F0II+nfbNQ==
dependencies:
"@material/base" "^0.41.0"
"@material/list" "^0.44.0"
"@material/menu-surface" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/notched-outline@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/notched-outline/-/notched-outline-0.44.0.tgz#d5a2e1d649921575a7cd2e88ee4581e4a1809573"
integrity sha512-c3nqOqUQAmW3h4zBbZVbMRdf4nNTYm0tVwXIAwmcCs5nvAthEHnzHwwFddNP7/9Wju6LZ0uqWn6xlyKly0uipw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/radio@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/radio/-/radio-0.44.0.tgz#f4cacdfabc7d765aa000cb34c5a37966f6d4fd6d"
integrity sha512-ar7uhlfHuSwM9JUUjpv7pLDLE0p436cCMxNTpmMjWabfvo3pMWlExvk72Oj81tBgfxY/uASLB3oj4neudXu9JQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/ripple@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-0.44.0.tgz#98920ff8ec4bf5714c97df3d190f02f8a5b476cc"
integrity sha512-MlaW4nUDgzS0JOBfsUawXyTOilr0jn+xvTVn6PEaGh2rmhNA54AhixXvdsVUWE9lfmHAsZV0AJHz2z7nunNhbQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/feature-targeting" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/rtl@^0.42.0":
version "0.42.0"
resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-0.42.0.tgz#1836e78186c2d8b996f6fbf97adab203535335bc"
integrity sha512-VrnrKJzhmspsN8WXHuxxBZ69yM5IwhCUqWr1t1eNfw3ZEvEj7i1g3P31HGowKThIN1dc1Wh4LE14rCISWCtv5w==
"@material/select@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/select/-/select-0.44.0.tgz#8041b4fe6247d013b0f12685fbdf50aa9ff57b35"
integrity sha512-tw3/QIBLuRCT+5IXx4IPiJk7FzeGeR65JEizdRUItH8yzoIiQLs/b2i3KtHM2YBXHgeUcEBF2AOqPX2opdYhug==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.0"
"@material/line-ripple" "^0.43.0"
"@material/menu" "^0.44.0"
"@material/menu-surface" "^0.44.0"
"@material/notched-outline" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/selection-control@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/selection-control/-/selection-control-0.44.0.tgz#63d5c65a47a9f54f5a0316b5ecdb5e5f35108609"
integrity sha512-HgCAPnMVMEj4X4ILkFSifqtZ3Tcc5HkU+Lfk9g0807sCaN/qBKWkYKLH2WJUbW8uk+MXK7DgP1khtS5zzanJWA==
dependencies:
"@material/ripple" "^0.44.0"
"@material/shape@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/shape/-/shape-0.43.0.tgz#b877acfd8be8abc9ddcf6601eb60dd0588292415"
integrity sha512-KGnoQV4G2OQbMe5Lr5Xbk8XNlO93Qi/juxXtd2wrAfiaPmktD8ug0CwdVDOPBOmj9a0gX3Ofi9XWcoU+tLEVjg==
"@material/slider@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/slider/-/slider-0.44.0.tgz#2055df894eb725e541cde50a544719c07934755b"
integrity sha512-Lnn2fdUesXX4O0UpJzveEuOj+og+dXCwhal73u3l3NXEdc/eRgYxwWdF3ww4MmCZ786EwUmjb4vIc9olN4DO3A==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/snackbar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/snackbar/-/snackbar-0.44.0.tgz#d98672b849f5f295e4fac2d474a9c80f11945518"
integrity sha512-KhCrmJm8Zu/ZZPuRCGfMKsZ0vudINlNgTjlOau0kQ/UgR1xBUvLOE8NjyXZr0RQz5obyW7xpyIWIpscn0IUeyw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/button" "^0.44.0"
"@material/dom" "^0.41.0"
"@material/icon-button" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/switch@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/switch/-/switch-0.44.0.tgz#f2cbb447437b12eb3bc7f0ec8318dbd3b4f0afce"
integrity sha512-EadCg6lHUF260R2Q/l++vXIITqacvbXlobSoewA5ib6y9BU2g7l13wL1W8xAVJNUMgFa/PyN+EKT3oCql7jZLg==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/selection-control" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/tab-bar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/tab-bar/-/tab-bar-0.44.0.tgz#b17d791bd557b1d84892fef1a1d8b8d6fef7c6d6"
integrity sha512-kCrt05d61YXyY43SNc0dPGuqysbcLr/KRDBvzpXgE4gv2jCCVhhjAH10KPlx8pthp/UtvrYJHb34acAKEGzdHA==
dependencies:
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/tab" "^0.44.0"
"@material/tab-scroller" "^0.44.0"
"@material/tab-indicator@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/tab-indicator/-/tab-indicator-0.43.0.tgz#37fd05513ba55ae218d9068c986c2676096fd6eb"
integrity sha512-RMNMQpWYghWpM6d0ayfuHEPzTiebKG0bMthViiD6tly8PubmOT8mShNhPm8ihybhDPUOLSz+7V4QNE5wikLEYg==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/theme" "^0.43.0"
"@material/tab-scroller@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/tab-scroller/-/tab-scroller-0.44.0.tgz#82d092ed45d2ee9d82038bed318e6ff6bdc36dad"
integrity sha512-Ufd3NWBN11kY2oA7bGmTYWGP1uz2mq0tfDM0JOiqoLMgD7y3Z18kmxnpq2qkg1vi4kvix28hBYGGMfLlq9rGDA==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/tab" "^0.44.0"
"@material/tab@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/tab/-/tab-0.44.0.tgz#254b92cff99015f0bd59a86d08d3f1c4744d0742"
integrity sha512-czrbGjtKkmUS3iYBX523xT5GOkjP0h+0x9fTnw+heFNpw5dCn6cZvlj3D9ayZU+ZH93x68TFhFVBuLU5f0EBXw==
dependencies:
"@material/base" "^0.41.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/tab-indicator" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/textfield@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/textfield/-/textfield-0.44.0.tgz#277b33948ddff33f7f643323895e5a683f013601"
integrity sha512-IMBwMcE82eVU+Wifpu0t84tozvBPLCeqQELDtZNYujKg3RxaultzJLwIyGKPMZ9R4yPEpV2vgXPGKE+2/AWt0g==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/floating-label" "^0.44.0"
"@material/line-ripple" "^0.43.0"
"@material/notched-outline" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/theme@^0.43.0":
version "0.43.0"
resolved "https://registry.yarnpkg.com/@material/theme/-/theme-0.43.0.tgz#6d9fa113c82e841817882172c152d60d2d203ca6"
integrity sha512-/zndZL6EihI18v2mYd4O8xvOBAAXmLeHyHVK28LozSAaJ9okQgD25wq5Ktk95oMTmPIC+rH66KcK6371ivNk8g==
"@material/toolbar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/toolbar/-/toolbar-0.44.0.tgz#6689aecdeccc78b7a890a3abbe8b68a2c6339307"
integrity sha512-YgLlOFQ5VzFLQBpXYSMviEbYox0fia+sasHuYPUhTAtas1ExVt9EEiIolDSVvhv2PruTReKKefxSbXAqGlOHog==
dependencies:
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/top-app-bar@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/top-app-bar/-/top-app-bar-0.44.0.tgz#2495c7f9567568fb961ccced24f479c4806a72af"
integrity sha512-tf0yXQJARYs8UPaH8oo3LnsSHEiur7Zm8Fc3hv3F0gNRRaZYBjwsMQMVbZZaWoQCWskMALyntBg+Fo18zdgDxw==
dependencies:
"@material/animation" "^0.41.0"
"@material/base" "^0.41.0"
"@material/elevation" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/shape" "^0.43.0"
"@material/theme" "^0.43.0"
"@material/typography" "^0.44.0"
"@material/typography@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@material/typography/-/typography-0.44.0.tgz#cf61dce2ee89bfa084d86e1b0f270a585bf9dfaf"
integrity sha512-m4SjA9OjZRDKowN3cPzEa8e2GlTlEn3ZmW/Fy9eRNSp83iY+8a0xl6kCaF80v5qAVwVcpfEFyEHWxMJtkBw2uA==
"@types/prop-types@*":
version "15.5.8"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.8.tgz#8ae4e0ea205fe95c3901a5a1df7f66495e3a56ce"
integrity sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw==
"@types/react-dom@^16.8.0":
version "16.8.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.0.tgz#c565f43f9d2ec911f9e0b8f3b74e25e67879aa3f"
integrity sha512-Jp4ufcEEjVJEB0OHq2MCZcE1u3KYUKO6WnSuiU/tZeYeiZxUoQavfa/TZeiIT+1XoN6l0lQVNM30VINZFDeolQ==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^16.8.2":
version "16.8.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.2.tgz#3b7a7f7ea89d1c7d68b00849fb5de839011c077b"
integrity sha512-6mcKsqlqkN9xADrwiUz2gm9Wg4iGnlVGciwBRYFQSMWG6MQjhOZ/AVnxn+6v8nslFgfYTV8fNdE6XwKu6va5PA==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
csstype@^2.2.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.2.tgz#3043d5e065454579afc7478a18de41909c8a2f01"
integrity sha512-Rl7PvTae0pflc1YtxtKbiSqq20Ts6vpIYOD5WBafl4y123DyHUeLrRdQP66sQW8/6gmX8jrYJLXwNeMqYVJcow==
focus-trap@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-4.0.2.tgz#4ee2b96547c9ea0e4252a2d4b2cca68944194663"
integrity sha512-HtLjfAK7Hp2qbBtLS6wEznID1mPT+48ZnP2nkHzgjpL4kroYHg0CdqJ5cTXk+UO5znAxF5fRUkhdyfgrhh8Lzw==
dependencies:
tabbable "^3.1.2"
xtend "^4.0.1"
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
material-components-web@^0.44.0:
version "0.44.0"
resolved "https://registry.yarnpkg.com/material-components-web/-/material-components-web-0.44.0.tgz#ff782e8d7bdd8212d3c6022a731258d0d42da531"
integrity sha512-BSRLf58SMVhAvlDhJDlcgYuvzeMwbMHKTJ7oIB8LaM24ZpXBxP9XCYJpKheMtiVLrgllCGDlJ/47OIDReHQXdQ==
dependencies:
"@material/animation" "^0.41.0"
"@material/auto-init" "^0.41.0"
"@material/base" "^0.41.0"
"@material/button" "^0.44.0"
"@material/card" "^0.44.0"
"@material/checkbox" "^0.44.0"
"@material/chips" "^0.44.0"
"@material/dialog" "^0.44.0"
"@material/dom" "^0.41.0"
"@material/drawer" "^0.44.0"
"@material/elevation" "^0.44.0"
"@material/fab" "^0.44.0"
"@material/feature-targeting" "^0.44.0"
"@material/floating-label" "^0.44.0"
"@material/form-field" "^0.44.0"
"@material/grid-list" "^0.44.0"
"@material/icon-button" "^0.44.0"
"@material/icon-toggle" "^0.44.0"
"@material/image-list" "^0.44.0"
"@material/layout-grid" "^0.41.0"
"@material/line-ripple" "^0.43.0"
"@material/linear-progress" "^0.43.0"
"@material/list" "^0.44.0"
"@material/menu" "^0.44.0"
"@material/menu-surface" "^0.44.0"
"@material/notched-outline" "^0.44.0"
"@material/radio" "^0.44.0"
"@material/ripple" "^0.44.0"
"@material/rtl" "^0.42.0"
"@material/select" "^0.44.0"
"@material/selection-control" "^0.44.0"
"@material/shape" "^0.43.0"
"@material/slider" "^0.44.0"
"@material/snackbar" "^0.44.0"
"@material/switch" "^0.44.0"
"@material/tab" "^0.44.0"
"@material/tab-bar" "^0.44.0"
"@material/tab-indicator" "^0.43.0"
"@material/tab-scroller" "^0.44.0"
"@material/textfield" "^0.44.0"
"@material/theme" "^0.43.0"
"@material/toolbar" "^0.44.0"
"@material/top-app-bar" "^0.44.0"
"@material/typography" "^0.44.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
prop-types@^15.6.2:
version "15.7.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.1.tgz#2fa61e0a699d428b40320127733ee2931f05d9d1"
integrity sha512-f8Lku2z9kERjOCcnDOPm68EBJAO2K00Q5mSgPAUE/gJuBgsYLbVy6owSrtcHj90zt8PvW+z0qaIIgsIhHOa1Qw==
dependencies:
object-assign "^4.1.1"
react-is "^16.8.1"
react-dom@^16.8.1:
version "16.8.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.1.tgz#ec860f98853d09d39bafd3a6f1e12389d283dbb4"
integrity sha512-N74IZUrPt6UiDjXaO7UbDDFXeUXnVhZzeRLy/6iqqN1ipfjrhR60Bp5NuBK+rv3GMdqdIuwIl22u1SYwf330bg==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.13.1"
react-is@^16.8.1:
version "16.8.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.1.tgz#a80141e246eb894824fb4f2901c0c50ef31d4cdb"
integrity sha512-ioMCzVDWvCvKD8eeT+iukyWrBGrA3DiFYkXfBsVYIRdaREZuBjENG+KjrikavCLasozqRWTwFUagU/O4vPpRMA==
react@^16.8.1:
version "16.8.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.1.tgz#ae11831f6cb2a05d58603a976afc8a558e852c4a"
integrity sha512-wLw5CFGPdo7p/AgteFz7GblI2JPOos0+biSoxf1FPsGxWQZdN/pj6oToJs1crn61DL3Ln7mN86uZ4j74p31ELQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.13.1"
scheduler@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.1.tgz#1a217df1bfaabaf4f1b92a9127d5d732d85a9591"
integrity sha512-VJKOkiKIN2/6NOoexuypwSrybx13MY7NSy9RNt8wPvZDMRT1CW6qlpF5jXRToXNHz3uWzbm2elNpZfXfGPqP9A==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
tabbable@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-3.1.2.tgz#f2d16cccd01f400e38635c7181adfe0ad965a4a2"
integrity sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ==
xtend@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=

View File

@ -1,4 +0,0 @@
{
"name": "@coder/disposable",
"main": "src/index.ts"
}

View File

@ -1,3 +0,0 @@
export interface IDisposable {
dispose(): void;
}

View File

@ -1 +0,0 @@
export * from "./disposable";

View File

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@ -1,18 +0,0 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
src
# Node.js dependencies:
node_modules/

View File

@ -1,8 +0,0 @@
FROM node
COPY out/main.js /main.js
COPY package.json /package.json
RUN yarn
ENV NODE_ENV production
CMD ["node", "/main.js"]

View File

@ -1,5 +0,0 @@
runtime: nodejs10
service: cdrdns
network:
forwarded_ports:
- 53/udp

View File

@ -1,14 +0,0 @@
{
"name": "@coder/dns",
"main": "out/main.js",
"scripts": {
"build": "../../node_modules/.bin/webpack --config ./webpack.config.js"
},
"dependencies": {
"node-named": "^0.0.1"
},
"devDependencies": {
"ip-address": "^5.8.9",
"@types/ip-address": "^5.8.2"
}
}

View File

@ -1,109 +0,0 @@
import { field, logger } from "@coder/logger";
import * as http from "http";
//@ts-ignore
import * as named from "node-named";
import * as ip from "ip-address";
import { words, wordKeys } from "./words";
import * as dgram from "dgram";
const oldCreate = dgram.createSocket;
// tslint:disable-next-line:no-any
(<any>dgram).createSocket = (_: any, callback: any): dgram.Socket => {
return oldCreate("udp4", callback);
};
interface DnsQuery {
name(): string;
// tslint:disable-next-line:no-any
addAnswer(domain: string, target: any, ttl: number): void;
}
const dnsServer: {
listen(port: number, host: string, callback: () => void): void;
on(event: "query", callback: (query: DnsQuery) => void): void;
send(query: DnsQuery): void;
} = named.createServer();
const isDev = process.env.NODE_ENV !== "production";
const dnsPort = isDev ? 9999 : 53;
dnsServer.listen(dnsPort, "0.0.0.0", () => {
logger.info("DNS server started", field("port", dnsPort));
});
dnsServer.on("query", (query) => {
const domain = query.name();
const reqParts = domain.split(".");
if (reqParts.length < 2) {
dnsServer.send(query);
logger.info("Invalid request", field("request", domain));
return;
}
const allWords = reqParts.shift()!;
if (allWords.length > 16) {
dnsServer.send(query);
logger.info("Invalid request", field("request", domain));
return;
}
const wordParts = allWords.split(/(?=[A-Z])/);
const ipParts: string[] = [];
// Should be left with HowAreYouNow
for (let i = 0; i < wordParts.length; i++) {
const part = wordParts[i];
if (part.length > 4) {
dnsServer.send(query);
logger.info("Words too long", field("request", domain));
return;
}
const ipPart = words[part.toLowerCase()];
if (typeof ipPart === "undefined") {
dnsServer.send(query);
logger.info("Word not found in index", field("part", part), field("request", domain));
return;
}
ipParts.push(ipPart.toString());
}
const address = new ip.Address4(ipParts.join("."));
if (address.isValid()) {
logger.info("Responded with valid address query", field("address", address.address), field("request", domain));
query.addAnswer(domain, new named.ARecord(address.address), 99999);
} else {
logger.warn("Received invalid request", field("request", domain));
}
dnsServer.send(query);
});
const httpServer = http.createServer((request, response) => {
const remoteAddr = request.connection.remoteAddress;
if (!remoteAddr) {
response.writeHead(422);
response.end();
return;
}
const hostHeader = request.headers.host;
if (!hostHeader) {
response.writeHead(422);
response.end();
return;
}
const host = remoteAddr.split(".").map(p => wordKeys[Number.parseInt(p, 10)]).map(s => s.charAt(0).toUpperCase() + s.slice(1)).join("");
logger.info("Resolved host", field("remote-addr", remoteAddr), field("host", host));
response.writeHead(200);
response.write(`${host}.${hostHeader}`);
response.end();
});
const httpPort = isDev ? 3000 : 80;
httpServer.listen(httpPort, "0.0.0.0", () => {
logger.info("HTTP server started", field("port", httpPort));
});

View File

@ -1,260 +0,0 @@
export const words: { readonly [key: string]: number } = {
term: 0,
salt: 1,
barn: 2,
corn: 3,
went: 4,
feel: 5,
rest: 6,
will: 7,
pale: 8,
cave: 9,
dirt: 10,
time: 11,
in: 12,
pie: 13,
star: 14,
iron: 15,
door: 16,
tone: 17,
want: 18,
task: 19,
zoo: 20,
nor: 21,
fall: 22,
tell: 23,
noon: 24,
new: 25,
per: 26,
end: 27,
arm: 28,
been: 29,
wolf: 30,
port: 31,
beat: 32,
pour: 33,
far: 34,
may: 35,
tie: 36,
moon: 37,
duck: 38,
us: 39,
led: 40,
met: 41,
bank: 42,
day: 43,
due: 44,
both: 45,
pet: 46,
gate: 47,
pain: 48,
rock: 49,
fill: 50,
open: 51,
thus: 52,
mark: 53,
our: 54,
loud: 55,
wife: 56,
say: 57,
flag: 58,
as: 59,
ride: 60,
once: 61,
sun: 62,
duty: 63,
pure: 64,
made: 65,
gulf: 66,
pig: 67,
fish: 68,
name: 69,
army: 70,
have: 71,
ill: 72,
meal: 73,
ago: 74,
late: 75,
view: 76,
atom: 77,
pen: 78,
mud: 79,
tail: 80,
sink: 81,
cow: 82,
rear: 83,
fur: 84,
go: 85,
suit: 86,
come: 87,
fear: 88,
also: 89,
sail: 90,
row: 91,
lay: 92,
noun: 93,
hat: 94,
am: 95,
mail: 96,
keep: 97,
drop: 98,
than: 99,
weak: 100,
by: 101,
who: 102,
fire: 103,
good: 104,
sick: 105,
care: 106,
pink: 107,
lady: 108,
war: 109,
sets: 110,
swam: 111,
well: 112,
shoe: 113,
bent: 114,
fuel: 115,
wet: 116,
fog: 117,
land: 118,
lead: 119,
tax: 120,
deal: 121,
verb: 122,
take: 123,
save: 124,
gift: 125,
had: 126,
gold: 127,
slow: 128,
drew: 129,
lamp: 130,
roof: 131,
hung: 132,
wild: 133,
able: 134,
girl: 135,
warn: 136,
were: 137,
know: 138,
camp: 139,
milk: 140,
neck: 141,
aid: 142,
fair: 143,
bell: 144,
dig: 145,
hope: 146,
wood: 147,
away: 148,
cook: 149,
just: 150,
form: 151,
food: 152,
hall: 153,
mind: 154,
for: 155,
card: 156,
half: 157,
sat: 158,
now: 159,
team: 160,
rush: 161,
face: 162,
wire: 163,
such: 164,
tool: 165,
make: 166,
fat: 167,
hold: 168,
inch: 169,
bill: 170,
mean: 171,
tide: 172,
burn: 173,
talk: 174,
tape: 175,
hard: 176,
mine: 177,
on: 178,
year: 179,
rich: 180,
sum: 181,
yes: 182,
baby: 183,
wide: 184,
how: 185,
clay: 186,
car: 187,
here: 188,
cent: 189,
bowl: 190,
post: 191,
said: 192,
see: 193,
raw: 194,
foot: 195,
life: 196,
bar: 197,
from: 198,
path: 199,
meat: 200,
show: 201,
sent: 202,
wait: 203,
mice: 204,
ten: 205,
pot: 206,
nice: 207,
idea: 208,
or: 209,
onto: 210,
rose: 211,
your: 212,
this: 213,
cat: 214,
bet: 215,
took: 216,
hang: 217,
very: 218,
bend: 219,
mix: 220,
base: 221,
jack: 222,
her: 223,
leg: 224,
own: 225,
book: 226,
love: 227,
dawn: 228,
deer: 229,
hit: 230,
rain: 231,
gas: 232,
eat: 233,
tube: 234,
case: 235,
pipe: 236,
get: 237,
joy: 238,
ever: 239,
nest: 240,
home: 241,
egg: 242,
pack: 243,
hand: 244,
cold: 245,
hot: 246,
frog: 247,
peep: 248,
seed: 249,
rawr: 250,
top: 251,
meow: 252,
bark: 253,
eel: 254,
swap: 255,
};
export const wordKeys = Object.keys(words);

View File

@ -1,18 +0,0 @@
const path = require("path");
const merge = require("webpack-merge");
const root = path.resolve(__dirname, "../..");
module.exports = merge(
require(path.join(root, "scripts/webpack.node.config.js"))({
dirname: __dirname,
name: "dns",
}), {
externals: {
"node-named": "commonjs node-named",
},
entry: [
"./packages/dns/src/dns.ts"
],
},
);

View File

@ -1,88 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/ip-address@^5.8.2":
version "5.8.2"
resolved "https://registry.yarnpkg.com/@types/ip-address/-/ip-address-5.8.2.tgz#5e413c477f78b3a264745eac937538a6e6e0c1f6"
integrity sha512-LFlDGRjJDnahfPyNCZGXvlaevSmZTi/zDxjTdXeTs8TQ9pQkNZKbCWaJXW29a3bGPRsASqeO+jGgZlaTUi9jTw==
dependencies:
"@types/jsbn" "*"
"@types/jsbn@*":
version "1.2.29"
resolved "https://registry.yarnpkg.com/@types/jsbn/-/jsbn-1.2.29.tgz#28229bc0262c704a1506c3ed69a7d7e115bd7832"
integrity sha512-2dVz9LTEGWVj9Ov9zaDnpvqHFV+W4bXtU0EUEGAzWfdRNO3dlUuosdHpENI6/oQW+Kejn0hAjk6P/czs9h/hvg==
bunyan@0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-0.7.0.tgz#921065e70c936fe302a740e2c5605775beea2f42"
integrity sha1-khBl5wyTb+MCp0DixWBXdb7qL0I=
"coffee-script@>= 1.1.1":
version "1.12.7"
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==
ip-address@^5.8.9:
version "5.8.9"
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-5.8.9.tgz#6379277c23fc5adb20511e4d23ec2c1bde105dfd"
integrity sha512-7ay355oMN34iXhET1BmCJVsHjOTSItEEIIpOs38qUC23AIhOy+xIPnkrTuEFjeLMrTJ7m8KMXWgWfy/2Vn9sDw==
dependencies:
jsbn "1.1.0"
lodash.find "^4.6.0"
lodash.max "^4.0.1"
lodash.merge "^4.6.0"
lodash.padstart "^4.6.1"
lodash.repeat "^4.1.0"
sprintf-js "1.1.0"
ipaddr.js@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-0.1.1.tgz#28c6a7c116a021c555544f906ab1ad540b1d635a"
integrity sha1-KManwRagIcVVVE+QarGtVAsdY1o=
dependencies:
coffee-script ">= 1.1.1"
jsbn@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA=
lodash.find@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=
lodash.max@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.max/-/lodash.max-4.0.1.tgz#8735566c618b35a9f760520b487ae79658af136a"
integrity sha1-hzVWbGGLNan3YFILSHrnllivE2o=
lodash.merge@^4.6.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==
lodash.padstart@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b"
integrity sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=
lodash.repeat@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/lodash.repeat/-/lodash.repeat-4.1.0.tgz#fc7de8131d8c8ac07e4b49f74ffe829d1f2bec44"
integrity sha1-/H3oEx2MisB+S0n3T/6CnR8r7EQ=
node-named@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/node-named/-/node-named-0.0.1.tgz#3607b434cf237ab99440f5ff6d19c05e3a93e217"
integrity sha1-Nge0NM8jermUQPX/bRnAXjqT4hc=
dependencies:
bunyan "0.7.0"
ipaddr.js "0.1.1"
sprintf-js@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.0.tgz#cffcaf702daf65ea39bb4e0fa2b299cec1a1be46"
integrity sha1-z/yvcC2vZeo5u04PorKZzsGhvkY=

View File

@ -1,4 +0,0 @@
{
"name": "@coder/events",
"main": "./src/index.ts"
}

View File

@ -1,99 +0,0 @@
import { IDisposable } from "@coder/disposable";
export interface Event<T> {
(listener: (value: T) => void): IDisposable;
(id: number | string, listener: (value: T) => void): IDisposable;
}
/**
* Emitter typecasts for a single event type. You can optionally use IDs, but
* using undefined with IDs will not work. If you emit without an ID, *all*
* listeners regardless of their ID (or lack thereof) will receive the event.
* Similarly, if you listen without an ID you will get *all* events for any or
* no ID.
*/
export class Emitter<T> {
private listeners = <Array<(value: T) => void>>[];
private readonly idListeners = new Map<number | string, Array<(value: T) => void>>();
public get event(): Event<T> {
return (id: number | string | ((value: T) => void), cb?: (value: T) => void): IDisposable => {
if (typeof id !== "function") {
if (this.idListeners.has(id)) {
this.idListeners.get(id)!.push(cb!);
} else {
this.idListeners.set(id, [cb!]);
}
return {
dispose: (): void => {
if (this.idListeners.has(id)) {
const cbs = this.idListeners.get(id)!;
const i = cbs.indexOf(cb!);
if (i !== -1) {
cbs.splice(i, 1);
}
}
},
};
}
cb = id;
this.listeners.push(cb);
return {
dispose: (): void => {
const i = this.listeners.indexOf(cb!);
if (i !== -1) {
this.listeners.splice(i, 1);
}
},
};
};
}
/**
* Emit an event with a value.
*/
public emit(value: T): void;
public emit(id: number | string, value: T): void;
public emit(id: number | string | T, value?: T): void {
if ((typeof id === "number" || typeof id === "string") && typeof value !== "undefined") {
if (this.idListeners.has(id)) {
this.idListeners.get(id)!.forEach((cb) => cb(value!));
}
this.listeners.forEach((cb) => cb(value!));
} else {
this.idListeners.forEach((cbs) => cbs.forEach((cb) => cb((id as T)!)));
this.listeners.forEach((cb) => cb((id as T)!));
}
}
/**
* Dispose the current events.
*/
public dispose(): void;
public dispose(id: number | string): void;
public dispose(id?: number | string): void {
if (typeof id !== "undefined") {
this.idListeners.delete(id);
} else {
this.listeners = [];
this.idListeners.clear();
}
}
public get counts(): { [key: string]: number } {
const counts = <{ [key: string]: number }>{};
if (this.listeners.length > 0) {
counts["n/a"] = this.listeners.length;
}
this.idListeners.forEach((cbs, id) => {
if (cbs.length > 0) {
counts[`${id}`] = cbs.length;
}
});
return counts;
}
}

View File

@ -1 +0,0 @@
export * from "./events";

View File

@ -1,122 +0,0 @@
import { Emitter } from "../src/events";
describe("Event", () => {
const emitter = new Emitter<number>();
it("should listen to global event", () => {
const fn = jest.fn();
const d = emitter.event(fn);
emitter.emit(10);
expect(fn).toHaveBeenCalledWith(10);
d.dispose();
});
it("should listen to id event", () => {
const fn = jest.fn();
const d = emitter.event(0, fn);
emitter.emit(0, 5);
expect(fn).toHaveBeenCalledWith(5);
d.dispose();
});
it("should listen to string id event", () => {
const fn = jest.fn();
const d = emitter.event("string", fn);
emitter.emit("string", 55);
expect(fn).toHaveBeenCalledWith(55);
d.dispose();
});
it("should not listen wrong id event", () => {
const fn = jest.fn();
const d = emitter.event(1, fn);
emitter.emit(0, 5);
emitter.emit(1, 6);
expect(fn).toHaveBeenCalledWith(6);
expect(fn).toHaveBeenCalledTimes(1);
d.dispose();
});
it("should listen to id event globally", () => {
const fn = jest.fn();
const d = emitter.event(fn);
emitter.emit(1, 11);
expect(fn).toHaveBeenCalledWith(11);
d.dispose();
});
it("should listen to global event", () => {
const fn = jest.fn();
const d = emitter.event(3, fn);
emitter.emit(14);
expect(fn).toHaveBeenCalledWith(14);
d.dispose();
});
it("should listen to id event multiple times", () => {
const fn = jest.fn();
const disposers = [
emitter.event(934, fn),
emitter.event(934, fn),
emitter.event(934, fn),
emitter.event(934, fn),
];
emitter.emit(934, 324);
expect(fn).toHaveBeenCalledTimes(4);
expect(fn).toHaveBeenCalledWith(324);
disposers.forEach((d) => d.dispose());
});
it("should dispose individually", () => {
const fn = jest.fn();
const d = emitter.event(fn);
const fn2 = jest.fn();
const d2 = emitter.event(1, fn2);
d.dispose();
emitter.emit(12);
emitter.emit(1, 12);
expect(fn).not.toBeCalled();
expect(fn2).toBeCalledTimes(2);
d2.dispose();
emitter.emit(12);
emitter.emit(1, 12);
expect(fn).not.toBeCalled();
expect(fn2).toBeCalledTimes(2);
});
it("should dispose by id", () => {
const fn = jest.fn();
emitter.event(fn);
const fn2 = jest.fn();
emitter.event(1, fn2);
emitter.dispose(1);
emitter.emit(12);
emitter.emit(1, 12);
expect(fn).toBeCalledTimes(2);
expect(fn2).not.toBeCalled();
});
it("should dispose all", () => {
const fn = jest.fn();
emitter.event(fn);
emitter.event(1, fn);
emitter.dispose();
emitter.emit(12);
emitter.emit(1, 12);
expect(fn).not.toBeCalled();
});
});

View File

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@ -1,5 +0,0 @@
# ide-api
Provides window listeners for interfacing with the IDE.
Created for content-scripts.

View File

@ -1,235 +0,0 @@
// tslint:disable no-any
import { ITerminalService } from "vs/workbench/contrib/terminal/common/terminal";
import { IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
export interface EvalHelper { }
interface ActiveEvalEmitter {
removeAllListeners(event?: string): void;
emit(event: string, ...args: any[]): void;
on(event: string, cb: (...args: any[]) => void): void;
}
interface IDisposable {
dispose(): void;
}
interface Disposer extends IDisposable {
onDidDispose: (cb: () => void) => void;
}
interface Event<T> {
(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
}
interface IAction extends IDisposable {
id: string;
label: string;
tooltip: string;
class: string | undefined;
enabled: boolean;
checked: boolean;
radio: boolean;
run(event?: any): Promise<any>;
}
interface IStatusbarEntry {
readonly text: string;
readonly tooltip?: string;
readonly color?: string;
readonly command?: string;
readonly arguments?: any[];
readonly showBeak?: boolean;
}
interface IStatusbarService {
addEntry(entry: IStatusbarEntry, alignment: ide.StatusbarAlignment, priority?: number): IDisposable;
setStatusMessage(message: string, autoDisposeAfter?: number, delayBy?: number): IDisposable;
}
type NotificationMessage = string | Error;
interface INotificationProperties {
sticky?: boolean;
silent?: boolean;
}
interface INotification extends INotificationProperties {
severity: ide.Severity;
message: NotificationMessage;
source?: string;
actions?: INotificationActions;
}
interface INotificationActions {
primary?: IAction[];
secondary?: IAction[];
}
interface INotificationProgress {
infinite(): void;
total(value: number): void;
worked(value: number): void;
done(): void;
}
interface INotificationHandle {
readonly onDidClose: Event<void>;
readonly progress: INotificationProgress;
updateSeverity(severity: ide.Severity): void;
updateMessage(message: NotificationMessage): void;
updateActions(actions?: INotificationActions): void;
close(): void;
}
interface IPromptChoice {
label: string;
isSecondary?: boolean;
keepOpen?: boolean;
run: () => void;
}
interface IPromptOptions extends INotificationProperties {
onCancel?: () => void;
}
interface INotificationService {
notify(notification: INotification): INotificationHandle;
info(message: NotificationMessage | NotificationMessage[]): void;
warn(message: NotificationMessage | NotificationMessage[]): void;
error(message: NotificationMessage | NotificationMessage[]): void;
prompt(severity: ide.Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle;
}
interface IBaseCommandAction {
id: string;
title: string;
category?: string;
}
interface ICommandAction extends IBaseCommandAction {
// iconLocation?: { dark: URI; light?: URI; };
// precondition?: ContextKeyExpr;
// toggled?: ContextKeyExpr;
}
interface ISerializableCommandAction extends IBaseCommandAction {
// iconLocation?: { dark: UriComponents; light?: UriComponents; };
}
interface IMenuItem {
command: ICommandAction;
alt?: ICommandAction;
// when?: ContextKeyExpr;
group?: "navigation" | string;
order?: number;
}
interface IMenuRegistry {
appendMenuItem(menu: ide.MenuId, item: IMenuItem): IDisposable;
}
export interface ICommandHandler {
(accessor: any, ...args: any[]): void;
}
export interface ICommand {
id: string;
handler: ICommandHandler;
description?: ICommandHandlerDescription | null;
}
export interface ICommandHandlerDescription {
description: string;
args: { name: string; description?: string; }[];
returns?: string;
}
interface ICommandRegistry {
registerCommand(command: ICommand): IDisposable;
}
interface IStorageService {
save(): Promise<void>;
}
declare namespace ide {
export const client: {};
export const workbench: {
readonly action: Action,
readonly syncActionDescriptor: SyncActionDescriptor,
readonly statusbarService: IStatusbarService;
readonly actionsRegistry: IWorkbenchActionRegistry;
readonly notificationService: INotificationService;
readonly storageService: IStorageService;
readonly menuRegistry: IMenuRegistry;
readonly commandRegistry: ICommandRegistry;
readonly terminalService: ITerminalService;
onFileCreate(cb: (path: string) => void): void;
onFileMove(cb: (path: string, target: string) => void): void;
onFileDelete(cb: (path: string) => void): void;
onFileSaved(cb: (path: string) => void): void;
onFileCopy(cb: (path: string, target: string) => void): void;
onModelAdded(cb: (path: string, languageId: string) => void): void;
onModelRemoved(cb: (path: string, languageId: string) => void): void;
onModelLanguageChange(cb: (path: string, languageId: string, oldLanguageId: string) => void): void;
onTerminalAdded(cb: () => void): void;
onTerminalRemoved(cb: () => void): void;
};
export enum Severity {
Ignore = 0,
Info = 1,
Warning = 2,
Error = 3,
}
export enum StatusbarAlignment {
LEFT = 0,
RIGHT = 1,
}
export enum MenuId {
CommandPalette,
DebugBreakpointsContext,
DebugCallStackContext,
DebugConsoleContext,
DebugVariablesContext,
DebugWatchContext,
EditorContext,
EditorTitle,
EditorTitleContext,
EmptyEditorGroupContext,
ExplorerContext,
MenubarAppearanceMenu,
MenubarDebugMenu,
MenubarEditMenu,
MenubarFileMenu,
MenubarGoMenu,
MenubarHelpMenu,
MenubarLayoutMenu,
MenubarNewBreakpointMenu,
MenubarPreferencesMenu,
MenubarRecentMenu,
MenubarSelectionMenu,
MenubarSwitchEditorMenu,
MenubarSwitchGroupMenu,
MenubarTerminalMenu,
MenubarViewMenu,
OpenEditorsContext,
ProblemsPanelContext,
SCMChangeContext,
SCMResourceContext,
SCMResourceGroupContext,
SCMSourceControl,
SCMTitle,
SearchContext,
TouchBarContext,
ViewItemContext,
ViewTitle,
}
}
declare global {
interface Window {
ide?: typeof ide;
addEventListener(event: "ide-ready", callback: (ide: CustomEvent & { readonly ide: typeof ide }) => void): void;
}
}

View File

@ -1,8 +0,0 @@
{
"name": "@coder/ide-api",
"version": "1.0.4",
"typings": "api.d.ts",
"author": "Coder",
"license": "MIT",
"description": "API for interfacing with the API created for content-scripts"
}

View File

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@ -1,5 +0,0 @@
{
"name": "@coder/ide",
"description": "Browser-based IDE client abstraction.",
"main": "src/index.ts"
}

View File

@ -1,139 +0,0 @@
import { field, logger, time, Time } from "@coder/logger";
import { SharedProcessData } from "@coder/protocol";
import { retry } from "./retry";
import { upload } from "./upload";
import { client } from "./fill/client";
import { clipboard } from "./fill/clipboard";
import { INotificationService, IProgressService } from "./fill/notification";
import "./fill/os"; // Ensure it fills before anything else waiting on initData.
/**
* A general abstraction of an IDE client.
*
* Everything the client provides is asynchronous so you can wait on what
* you need from it without blocking anything else.
*
* It also provides task management to help asynchronously load and time code.
*/
export abstract class IdeClient {
public readonly retry = retry;
public readonly clipboard = clipboard;
public readonly upload = upload;
private start: Time | undefined;
private readonly tasks = <string[]>[];
private finishedTaskCount = 0;
private readonly loadTime: Time;
public readonly initData = client.initData;
public readonly sharedProcessData: Promise<SharedProcessData>;
public readonly onSharedProcessActive = client.onSharedProcessActive;
public constructor() {
logger.info("Loading IDE");
this.loadTime = time(2500);
let appWindow: Window | undefined;
window.addEventListener("beforeunload", (e) => {
e.preventDefault(); // FireFox
e.returnValue = ""; // Chrome
});
window.addEventListener("message", (event) => {
if (event.data === "app") {
appWindow = event.source as Window;
}
});
this.sharedProcessData = new Promise((resolve): void => {
let d = client.onSharedProcessActive((data) => {
d.dispose();
d = client.onSharedProcessActive(() => {
d.dispose();
this.retry.notificationService.error(
new Error("Disconnected from shared process. Searching, installing, enabling, and disabling extensions will not work until the page is refreshed."),
);
});
resolve(data);
});
});
window.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
// Prevent Firefox from trying to reconnect when the page unloads.
window.addEventListener("unload", () => {
this.retry.block();
logger.info("Unloaded");
});
this.initialize().then(() => {
logger.info("Load completed", field("duration", this.loadTime));
if (appWindow) {
appWindow.postMessage("loaded", "*");
}
}).catch((error) => {
logger.error(error.message);
logger.warn("Load completed with errors", field("duration", this.loadTime));
});
}
public async task<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
public async task<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
public async task<T, V1, V2>(description: string, duration: number, task: (v1: V1, v2: V2) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>): Promise<T>;
public async task<T, V1, V2, V3>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>): Promise<T>;
public async task<T, V1, V2, V3, V4>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>): Promise<T>;
public async task<T, V1, V2, V3, V4, V5>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>): Promise<T>;
public async task<T, V1, V2, V3, V4, V5, V6>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>, t6: Promise<V6>): Promise<T>;
/**
* Wrap a task in some logging, timing, and progress updates. Can optionally
* wait on other tasks which won't count towards this task's time.
*/
public async task<T>(
description: string, duration: number = 100, task: (...args: any[]) => Promise<T>, ...after: Array<Promise<any>> // tslint:disable-line no-any
): Promise<T> {
this.tasks.push(description);
if (!this.start) {
this.start = time(1000);
}
let start: Time | undefined;
try {
const waitFor = await (after && after.length > 0 ? Promise.all(after) : Promise.resolve([]));
start = time(duration);
logger.info(description);
const value = await task(...waitFor);
logger.info(`Finished "${description}"`, field("duration", start));
const index = this.tasks.indexOf(description);
if (index !== -1) {
this.tasks.splice(index, 1);
}
++this.finishedTaskCount;
if (this.tasks.length === 0) {
logger.info("Finished all queued tasks", field("duration", this.start), field("count", this.finishedTaskCount));
this.start = undefined;
}
return value;
} catch (error) {
logger.error(`Failed "${description}"`, field("duration", typeof start !== "undefined" ? start : "not started"), field("error", error));
throw error;
}
}
public set notificationService(service: INotificationService) {
this.retry.notificationService = service;
this.upload.notificationService = service;
}
public set progressService(service: IProgressService) {
this.upload.progressService = service;
}
/**
* Initialize the IDE.
*/
protected abstract initialize(): Promise<void>;
}

View File

@ -1,4 +0,0 @@
import { Module } from "@coder/protocol";
import { client } from "./client";
export = client.modules[Module.ChildProcess];

View File

@ -1,148 +0,0 @@
import { Emitter } from "@coder/events";
import { field, logger } from "@coder/logger";
import { Client, ReadWriteConnection } from "@coder/protocol";
import { retry } from "../retry";
/**
* A connection based on a web socket. Automatically reconnects and buffers
* messages during connection.
*/
class WebsocketConnection implements ReadWriteConnection {
private activeSocket: WebSocket | undefined;
private readonly messageBuffer = <Uint8Array[]>[];
private readonly socketTimeoutDelay = 60 * 1000;
private readonly retry = retry.register("Socket", () => this.connect());
private isUp: boolean = false;
private closed: boolean = false;
private readonly messageEmitter = new Emitter<Uint8Array>();
private readonly closeEmitter = new Emitter<void>();
private readonly upEmitter = new Emitter<void>();
private readonly downEmitter = new Emitter<void>();
public readonly onUp = this.upEmitter.event;
public readonly onClose = this.closeEmitter.event;
public readonly onDown = this.downEmitter.event;
public readonly onMessage = this.messageEmitter.event;
public constructor() {
this.retry.block();
this.retry.run();
}
/**
* Send data across the socket. If closed, will error. If connecting, will
* queue.
*/
public send(data: Buffer | Uint8Array): void {
if (this.closed) {
throw new Error("web socket is closed");
}
if (!this.activeSocket || this.activeSocket.readyState !== this.activeSocket.OPEN) {
this.messageBuffer.push(data);
} else {
this.activeSocket.send(data);
}
}
/**
* Close socket connection.
*/
public close(): void {
this.closed = true;
this.dispose();
this.closeEmitter.emit();
}
/**
* Connect to the server.
*/
private async connect(): Promise<void> {
const socket = await this.openSocket();
socket.addEventListener("message", (event: MessageEvent) => {
this.messageEmitter.emit(event.data);
});
socket.addEventListener("close", (event) => {
if (this.isUp) {
this.isUp = false;
try {
this.downEmitter.emit(undefined);
} catch (error) {
// Don't let errors here prevent restarting.
logger.error(error.message);
}
}
logger.warn(
"Web socket closed",
field("code", event.code),
field("reason", event.reason),
field("wasClean", event.wasClean),
);
if (!this.closed) {
this.retry.block();
this.retry.run();
}
});
// Send any messages that were queued while we were waiting to connect.
while (this.messageBuffer.length > 0) {
socket.send(this.messageBuffer.shift()!);
}
if (!this.isUp) {
this.isUp = true;
this.upEmitter.emit(undefined);
}
}
/**
* Open a web socket, disposing the previous connection if any.
*/
private async openSocket(): Promise<WebSocket> {
this.dispose();
const wsProto = location.protocol === "https:" ? "wss" : "ws";
const socket = new WebSocket(
`${wsProto}://${location.host}${location.pathname}`,
);
socket.binaryType = "arraybuffer";
this.activeSocket = socket;
const socketWaitTimeout = window.setTimeout(() => {
socket.close();
}, this.socketTimeoutDelay);
await new Promise((resolve, reject): void => {
const doReject = (): void => {
clearTimeout(socketWaitTimeout);
socket.removeEventListener("error", doReject);
socket.removeEventListener("close", doReject);
reject();
};
socket.addEventListener("error", doReject);
socket.addEventListener("close", doReject);
socket.addEventListener("open", () => {
clearTimeout(socketWaitTimeout);
socket.removeEventListener("error", doReject);
socket.removeEventListener("close", doReject);
resolve();
});
});
return socket;
}
/**
* Dispose the current connection.
*/
private dispose(): void {
if (this.activeSocket) {
this.activeSocket.close();
}
}
}
// Global instance so all fills can use the same client.
export const client = new Client(new WebsocketConnection());

View File

@ -1,146 +0,0 @@
import { Emitter } from "@coder/events";
/**
* Wrapper around the native clipboard with some fallbacks.
*/
export class Clipboard {
private readonly enableEmitter = new Emitter<boolean>();
public readonly onPermissionChange = this.enableEmitter.event;
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();
};
});
}
}
/**
* Paste currently copied text.
*/
public async paste(): Promise<boolean> {
if (this.isEnabled) {
try {
const element = document.activeElement as HTMLInputElement | HTMLTextAreaElement;
const start = element.selectionStart || 0;
const end = element.selectionEnd;
const allText = element.value;
const newText = allText.substring(0, start)
+ (await this.readText())
+ allText.substring(end || start);
element.value = newText;
return true;
} catch (ex) {
// Will try execCommand below.
}
}
return document.execCommand("paste");
}
/**
* 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
}
/**
* 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

@ -1,83 +0,0 @@
.msgbox {
padding-top: 25px;
padding-left: 40px;
padding-right: 40px;
padding-bottom: 25px;
background: #242424;
-webkit-box-shadow: 0px 0px 10px -3px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 10px -3px rgba(0,0,0,0.75);
box-shadow: 0px 0px 10px -3px rgba(0,0,0,0.75);
border-radius: 3px;
}
.msgbox.input {
max-width: 500px;
width: 100%;
}
.msgbox > .input {
background: #141414;
border: none;
box-sizing: border-box;
padding: 10px;
width: 100%;
}
.msgbox > .msg {
font-size: 16px;
font-weight: bold;
}
.msgbox > .detail {
font-size: 14px;
margin: 5px 0;
}
.msgbox > .errors {
margin-top: 20px;
}
.msgbox > .errors {
color: #f44747;
}
.msgbox > .button-wrapper {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 20px;
}
.msgbox > .button-wrapper > button {
flex: 1;
border-radius: 2px;
padding: 10px;
color: white;
background: #3d3d3d;
border: 0px;
cursor: pointer;
opacity: 0.8;
}
.msgbox > .button-wrapper > button:hover {
opacity: 1;
}
.msgbox > .button-wrapper > button:not(:last-child) {
margin-right: 8px;
}
.msgbox-overlay {
align-items: center;
background: rgba(0, 0, 0, 0.4);
bottom: 0;
display: flex;
justify-content: center;
left: 0;
opacity: 0;
position: absolute;
right: 0;
top: 0;
transition: 300ms opacity ease;
z-index: 15;
}

View File

@ -1,176 +0,0 @@
import { Emitter } from "@coder/events";
import "./dialog.scss";
export interface IDialogOptions {
message?: string;
detail?: string;
buttons?: string[];
input?: {
value: string;
selection?: {
start: number;
end: number;
};
};
}
export interface IDialogAction {
buttonIndex?: number;
key?: IKey;
}
export enum IKey {
Enter = "Enter",
Escape = "Escape",
}
export class Dialog {
private readonly overlay: HTMLElement;
private cachedActiveElement: HTMLElement | undefined;
private input: HTMLInputElement | undefined;
private errors: HTMLElement;
private buttons: HTMLElement[] | undefined;
private readonly msgBox: HTMLElement;
private actionEmitter = new Emitter<IDialogAction>();
public onAction = this.actionEmitter.event;
public constructor(private readonly options: IDialogOptions) {
this.msgBox = document.createElement("div");
this.msgBox.classList.add("msgbox");
if (this.options.message) {
const messageDiv = document.createElement("div");
messageDiv.classList.add("msg");
messageDiv.innerText = this.options.message;
this.msgBox.appendChild(messageDiv);
}
if (this.options.detail) {
const detailDiv = document.createElement("div");
detailDiv.classList.add("detail");
detailDiv.innerText = this.options.detail;
this.msgBox.appendChild(detailDiv);
}
if (this.options.input) {
this.msgBox.classList.add("input");
this.input = document.createElement("input");
this.input.classList.add("input");
this.input.value = this.options.input.value;
this.input.addEventListener("keydown", (event) => {
if (event.key === IKey.Enter) {
event.preventDefault();
this.actionEmitter.emit({
buttonIndex: undefined,
key: IKey.Enter,
});
}
});
this.msgBox.appendChild(this.input);
}
this.errors = document.createElement("div");
this.errors.classList.add("errors");
if (this.options.buttons && this.options.buttons.length > 0) {
this.buttons = this.options.buttons.map((buttonText, buttonIndex) => {
const button = document.createElement("button");
// TODO: support mnemonics.
button.innerText = buttonText.replace("&&", "");
button.addEventListener("click", () => {
this.actionEmitter.emit({
buttonIndex,
key: undefined,
});
});
return button;
});
const buttonWrapper = document.createElement("div");
buttonWrapper.classList.add("button-wrapper");
this.buttons.forEach((b) => buttonWrapper.appendChild(b));
this.msgBox.appendChild(buttonWrapper);
}
this.overlay = document.createElement("div");
this.overlay.className = "msgbox-overlay";
this.overlay.appendChild(this.msgBox);
setTimeout(() => {
this.overlay.style.opacity = "1";
});
}
/**
* Input value if this dialog has an input.
*/
public get inputValue(): string | undefined {
return this.input ? this.input.value : undefined;
}
/**
* Display or remove an error.
*/
public set error(error: string | undefined) {
while (this.errors.lastChild) {
this.errors.removeChild(this.errors.lastChild);
}
if (error) {
const errorDiv = document.createElement("error");
errorDiv.innerText = error;
this.errors.appendChild(errorDiv);
this.msgBox.appendChild(this.errors);
}
}
/**
* Show the dialog.
*/
public show(): void {
if (!this.cachedActiveElement) {
this.cachedActiveElement = document.activeElement as HTMLElement;
(document.querySelector(".monaco-workbench") || document.body).appendChild(this.overlay);
document.addEventListener("keydown", this.onKeydown);
if (this.input) {
this.input.focus();
if (this.options.input && this.options.input.selection) {
this.input.setSelectionRange(
this.options.input.selection.start,
this.options.input.selection.end,
);
}
} else if (this.buttons) {
this.buttons[0].focus();
}
}
}
/**
* Remove the dialog and clean up.
*/
public hide(): void {
if (this.cachedActiveElement) {
this.overlay.remove();
document.removeEventListener("keydown", this.onKeydown);
this.cachedActiveElement.focus();
this.cachedActiveElement = undefined;
}
}
/**
* Capture escape.
*/
private onKeydown = (event: KeyboardEvent): void => {
if (event.key === "Escape") {
event.preventDefault();
event.stopPropagation();
this.actionEmitter.emit({
buttonIndex: undefined,
key: IKey.Escape,
});
}
}
}

View File

@ -1,490 +0,0 @@
/// <reference path="../../../../lib/vscode/src/typings/electron.d.ts" />
import { EventEmitter } from "events";
import * as fs from "fs";
import * as trash from "trash";
import { logger, field } from "@coder/logger";
import { IKey, Dialog as DialogBox } from "./dialog";
import { clipboard } from "./clipboard";
// tslint:disable-next-line no-any
(global as any).getOpenUrls = (): string[] => {
return [];
};
// This is required to make the fill load in Node without erroring.
if (typeof document === "undefined") {
// tslint:disable-next-line no-any
(global as any).document = {} as any;
}
const oldCreateElement = document.createElement;
const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K] => {
const createElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K] => {
// tslint:disable-next-line:no-any
return oldCreateElement.call(document, tagName as any);
};
// tslint:disable-next-line:no-any
const getPropertyDescriptor = (object: any, id: string): PropertyDescriptor | undefined => {
let op = Object.getPrototypeOf(object);
while (!Object.getOwnPropertyDescriptor(op, id)) {
op = Object.getPrototypeOf(op);
}
return Object.getOwnPropertyDescriptor(op, id);
};
if (tagName === "img") {
const img = createElement("img");
const oldSrc = getPropertyDescriptor(img, "src");
if (!oldSrc) {
throw new Error("Failed to find src property");
}
Object.defineProperty(img, "src", {
get: (): string => {
return oldSrc!.get!.call(img);
},
set: (value: string): void => {
if (value) {
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
value = value.replace(/file:\/\//g, resourceBaseUrl);
}
oldSrc!.set!.call(img, value);
},
});
return img;
}
if (tagName === "style") {
const style = createElement("style");
const oldInnerHtml = getPropertyDescriptor(style, "innerHTML");
if (!oldInnerHtml) {
throw new Error("Failed to find innerHTML property");
}
Object.defineProperty(style, "innerHTML", {
get: (): string => {
return oldInnerHtml!.get!.call(style);
},
set: (value: string): void => {
if (value) {
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
value = value.replace(/file:\/\//g, resourceBaseUrl);
}
oldInnerHtml!.set!.call(style, value);
},
});
let overridden = false;
const oldSheet = getPropertyDescriptor(style, "sheet");
Object.defineProperty(style, "sheet", {
// tslint:disable-next-line:no-any
get: (): any => {
const sheet = oldSheet!.get!.call(style);
if (sheet && !overridden) {
const oldInsertRule = sheet.insertRule;
sheet.insertRule = (rule: string, index?: number): void => {
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
rule = rule.replace(/file:\/\//g, resourceBaseUrl);
oldInsertRule.call(sheet, rule, index);
};
overridden = true;
}
return sheet;
},
});
return style;
}
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 = createElement("script");
script.src = url;
script.addEventListener("load", () => {
view.contentDocument!.dispatchEvent(new Event("DOMContentLoaded", {
bubbles: true,
cancelable: true,
}));
// const e = new CustomEvent("ipc-message");
// (e as any).channel = "webview-ready"; // tslint:disable-line no-any
// (e as any).args = [frameID]; // tslint:disable-line no-any
// view.dispatchEvent(e);
});
view.contentDocument.head.appendChild(script);
}
};
},
});
view.src = require("!!file-loader?name=[path][name].[ext]!./webview.html");
Object.defineProperty(view, "src", {
set: (): void => { /* Nope. */ },
});
(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
const resourceBaseUrl = location.pathname.replace(/\/$/, "") + "/resource";
args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m1) => `"${resourceBaseUrl}${m1}"`);
args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m, m1) => `"${resourceBaseUrl}${m1}"`);
args[0].contents = (args[0].contents as string).replace(/style-src vscode-core-resource:/g, "style-src 'self'");
}
if (view.contentWindow) {
view.contentWindow.postMessage({
channel,
data: args,
id: frameID,
}, "*");
}
};
return view;
}
return createElement(tagName);
};
document.createElement = newCreateElement;
class Clipboard {
private readonly buffers = new Map<string, Buffer>();
public has(format: string): boolean {
return this.buffers.has(format);
}
public readFindText(): string {
return "";
}
public writeFindText(_text: string): void {
// Nothing.
}
public writeText(value: string): Promise<void> {
return clipboard.writeText(value);
}
public readText(): Promise<string> {
return clipboard.readText();
}
public writeBuffer(format: string, buffer: Buffer): void {
this.buffers.set(format, buffer);
}
public readBuffer(format: string): Buffer | undefined {
return this.buffers.get(format);
}
}
class Shell {
public async moveItemToTrash(path: string): Promise<void> {
await trash(path);
}
}
class App extends EventEmitter {
public isAccessibilitySupportEnabled(): boolean {
return false;
}
public setAsDefaultProtocolClient(): void {
throw new Error("not implemented");
}
}
class Dialog {
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",
};
const dialog = new DialogBox(saveDialogOptions);
dialog.onAction((action) => {
if (action.key !== IKey.Enter && action.buttonIndex !== 1) {
dialog.hide();
return callback(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.";
return;
}
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;
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"],
});
confirmDialog.onAction((action) => {
if (action.buttonIndex === 1) {
confirmDialog.hide();
return callback(filePath);
}
confirmDialog.hide();
dialog.show();
});
dialog.hide();
confirmDialog.show();
}
});
});
dialog.show();
}
public showOpenDialog(): void {
throw new Error("not implemented");
}
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().catch((error) => {
logger.error(error.message);
});
} else {
document.exitFullscreen().catch((error) => {
logger.error(error.message);
});
}
}
public isFullScreen(): boolean {
// TypeScript doesn't recognize this property.
// tslint:disable no-any
if (typeof (window as any)["fullScreen"] !== "undefined") {
return (window as any)["fullScreen"];
}
// tslint:enable no-any
try {
return window.matchMedia("(display-mode: fullscreen)").matches;
} catch (error) {
logger.error(error.message);
return false;
}
}
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 +0,0 @@
export = {};

View File

@ -1,4 +0,0 @@
import { Module } from "@coder/protocol";
import { client } from "./client";
export = client.modules[Module.Fs];

View File

@ -1,4 +0,0 @@
import { Module } from "@coder/protocol";
import { client } from "./client";
export = client.modules[Module.Net];

View File

@ -1,79 +0,0 @@
import { logger, field } from "@coder/logger";
export interface INotificationHandle {
close(): void;
updateMessage(message: string): void;
updateButtons(buttons: INotificationButton[]): void;
}
export enum Severity {
Ignore = 0,
Info = 1,
Warning = 2,
Error = 3,
}
export interface INotificationButton {
label: string;
run(): void;
}
/**
* Optional notification service.
*/
export interface INotificationService {
error(error: Error): void;
prompt(severity: Severity, message: string, buttons: INotificationButton[], onCancel: () => void): INotificationHandle;
}
export interface IProgress {
/**
* Report progress, which should be the completed percentage from 0 to 100.
*/
report(progress: number): void;
}
export interface IProgressService {
/**
* Start a new progress bar that resolves & disappears when the task finishes.
*/
start<T>(title: string, task: (progress: IProgress) => Promise<T>, onCancel: () => void): Promise<T>;
}
/**
* Console-based notification service.
*/
export class NotificationService implements INotificationService {
public error(error: Error): void {
logger.error(error.message, field("error", error));
}
public prompt(severity: Severity, message: string, _buttons: INotificationButton[], _onCancel: () => void): INotificationHandle {
switch (severity) {
case Severity.Info: logger.info(message); break;
case Severity.Warning: logger.warn(message); break;
case Severity.Error: logger.error(message); break;
}
return {
close: (): void => undefined,
updateMessage: (): void => undefined,
updateButtons: (): void => undefined,
};
}
}
/**
* Console-based progress service.
*/
export class ProgressService implements IProgressService {
public start<T>(title: string, task: (progress: IProgress) => Promise<T>): Promise<T> {
logger.info(title);
return task({
report: (progress): void => {
logger.info(`${title} progress: ${progress}`);
},
});
}
}

View File

@ -1,57 +0,0 @@
import { OperatingSystem, InitData } from "@coder/protocol";
import { client } from "./client";
class OS {
private _homedir: string | undefined;
private _tmpdir: string | undefined;
private _platform: NodeJS.Platform | undefined;
public constructor() {
client.initData.then((d) => this.initialize(d));
}
public homedir(): string {
if (typeof this._homedir === "undefined") {
throw new Error("trying to access homedir before it has been set");
}
return this._homedir;
}
public tmpdir(): string {
if (typeof this._tmpdir === "undefined") {
throw new Error("trying to access tmpdir before it has been set");
}
return this._tmpdir;
}
public initialize(data: InitData): void {
this._homedir = data.homeDirectory;
this._tmpdir = data.tmpDirectory;
switch (data.os) {
case OperatingSystem.Windows: this._platform = "win32"; break;
case OperatingSystem.Mac: this._platform = "darwin"; break;
default: this._platform = "linux"; break;
}
process.platform = this._platform;
process.env = {};
data.env.forEach((v, k) => {
process.env[k] = v;
});
}
public release(): string {
return "Unknown";
}
public platform(): NodeJS.Platform {
if (typeof this._platform === "undefined") {
throw new Error("trying to access platform before it has been set");
}
return this._platform;
}
}
export = new OS();

View File

@ -1,518 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var os = require('os');
var util = require('util');
var isWindows = os.platform() === "win32";
// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
// if the path tries to go above the root, `up` ends up > 0
var up = 0;
for (var i = parts.length - 1; i >= 0; i--) {
var last = parts[i];
if (last === '.') {
parts.splice(i, 1);
} else if (last === '..') {
parts.splice(i, 1);
up++;
} else if (up) {
parts.splice(i, 1);
up--;
}
}
// if the path is allowed to go above the root, restore leading ..s
if (allowAboveRoot) {
for (; up--; up) {
parts.unshift('..');
}
}
return parts;
}
if (isWindows) {
// Regex to split a windows path into three parts: [*, device, slash,
// tail] windows-only
var splitDeviceRe =
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
// Regex to split the tail part of the above into [*, dir, basename, ext]
var splitTailRe =
/^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;
// Function to split a filename into [root, dir, basename, ext]
// windows version
var splitPath = function(filename) {
// Separate device+slash from tail
var result = splitDeviceRe.exec(filename),
device = (result[1] || '') + (result[2] || ''),
tail = result[3] || '';
// Split the tail into dir, basename and extension
var result2 = splitTailRe.exec(tail),
dir = result2[1],
basename = result2[2],
ext = result2[3];
return [device, dir, basename, ext];
};
var normalizeUNCRoot = function(device) {
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
};
// path.resolve([from ...], to)
// windows version
exports.resolve = function() {
var resolvedDevice = '',
resolvedTail = '',
resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1; i--) {
var path;
if (i >= 0) {
path = arguments[i];
} else if (!resolvedDevice) {
path = process.cwd();
} else {
// Windows has the concept of drive-specific current working
// directories. If we've resolved a drive letter but not yet an
// absolute path, get cwd for that drive. We're sure the device is not
// an unc path at this points, because unc paths are always absolute.
path = process.env['=' + resolvedDevice];
// Verify that a drive-local cwd was found and that it actually points
// to our drive. If not, default to the drive's root.
if (!path || path.substr(0, 3).toLowerCase() !==
resolvedDevice.toLowerCase() + '\\') {
path = resolvedDevice + '\\';
}
}
// Skip empty and invalid entries
if (!util.isString(path)) {
throw new TypeError('Arguments to path.resolve must be strings');
} else if (!path) {
continue;
}
var result = splitDeviceRe.exec(path),
device = result[1] || '',
isUnc = device && device.charAt(1) !== ':',
isAbsolute = exports.isAbsolute(path),
tail = result[3];
if (device &&
resolvedDevice &&
device.toLowerCase() !== resolvedDevice.toLowerCase()) {
// This path points to another device so it is not applicable
continue;
}
if (!resolvedDevice) {
resolvedDevice = device;
}
if (!resolvedAbsolute) {
resolvedTail = tail + '\\' + resolvedTail;
resolvedAbsolute = isAbsolute;
}
if (resolvedDevice && resolvedAbsolute) {
break;
}
}
// Convert slashes to backslashes when `resolvedDevice` points to an UNC
// root. Also squash multiple slashes into a single one where appropriate.
if (isUnc) {
resolvedDevice = normalizeUNCRoot(resolvedDevice);
}
// At this point the path should be resolved to a full absolute path,
// but handle relative paths to be safe (might happen when process.cwd()
// fails)
// Normalize the tail path
function f(p) {
return !!p;
}
resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
!resolvedAbsolute).join('\\');
return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
'.';
};
// windows version
exports.normalize = function(path) {
var result = splitDeviceRe.exec(path),
device = result[1] || '',
isUnc = device && device.charAt(1) !== ':',
isAbsolute = exports.isAbsolute(path),
tail = result[3],
trailingSlash = /[\\\/]$/.test(tail);
// If device is a drive letter, we'll normalize to lower case.
if (device && device.charAt(1) === ':') {
device = device[0].toLowerCase() + device.substr(1);
}
// Normalize the tail path
tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
return !!p;
}), !isAbsolute).join('\\');
if (!tail && !isAbsolute) {
tail = '.';
}
if (tail && trailingSlash) {
tail += '\\';
}
// Convert slashes to backslashes when `device` points to an UNC root.
// Also squash multiple slashes into a single one where appropriate.
if (isUnc) {
device = normalizeUNCRoot(device);
}
return device + (isAbsolute ? '\\' : '') + tail;
};
// windows version
exports.isAbsolute = function(path) {
var result = splitDeviceRe.exec(path),
device = result[1] || '',
isUnc = !!device && device.charAt(1) !== ':';
// UNC paths are always absolute
return !!result[2] || isUnc;
};
// windows version
exports.join = function() {
function f(p) {
if (!util.isString(p)) {
throw new TypeError('Arguments to path.join must be strings');
}
return p;
}
var paths = Array.prototype.filter.call(arguments, f);
var joined = paths.join('\\');
// Make sure that the joined path doesn't start with two slashes, because
// normalize() will mistake it for an UNC path then.
//
// This step is skipped when it is very clear that the user actually
// intended to point at an UNC path. This is assumed when the first
// non-empty string arguments starts with exactly two slashes followed by
// at least one more non-slash character.
//
// Note that for normalize() to treat a path as an UNC path it needs to
// have at least 2 components, so we don't filter for that here.
// This means that the user can use join to construct UNC paths from
// a server name and a share name; for example:
// path.join('//server', 'share') -> '\\\\server\\share\')
if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
joined = joined.replace(/^[\\\/]{2,}/, '\\');
}
return exports.normalize(joined);
};
// path.relative(from, to)
// it will solve the relative path from 'from' to 'to', for instance:
// from = 'C:\\orandea\\test\\aaa'
// to = 'C:\\orandea\\impl\\bbb'
// The output of the function should be: '..\\..\\impl\\bbb'
// windows version
exports.relative = function(from, to) {
from = exports.resolve(from);
to = exports.resolve(to);
// windows is not case sensitive
var lowerFrom = from.toLowerCase();
var lowerTo = to.toLowerCase();
function trim(arr) {
var start = 0;
for (; start < arr.length; start++) {
if (arr[start] !== '') break;
}
var end = arr.length - 1;
for (; end >= 0; end--) {
if (arr[end] !== '') break;
}
if (start > end) return [];
return arr.slice(start, end + 1);
}
var toParts = trim(to.split('\\'));
var lowerFromParts = trim(lowerFrom.split('\\'));
var lowerToParts = trim(lowerTo.split('\\'));
var length = Math.min(lowerFromParts.length, lowerToParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (lowerFromParts[i] !== lowerToParts[i]) {
samePartsLength = i;
break;
}
}
if (samePartsLength == 0) {
return to;
}
var outputParts = [];
for (var i = samePartsLength; i < lowerFromParts.length; i++) {
outputParts.push('..');
}
outputParts = outputParts.concat(toParts.slice(samePartsLength));
return outputParts.join('\\');
};
exports.sep = '\\';
exports.delimiter = ';';
} else /* posix */ {
// Split a filename into [root, dir, basename, ext], unix version
// 'root' is just a slash, or nothing.
var splitPathRe =
/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
var splitPath = function(filename) {
return splitPathRe.exec(filename).slice(1);
};
// path.resolve([from ...], to)
// posix version
exports.resolve = function() {
var resolvedPath = '',
resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
var path = (i >= 0) ? arguments[i] : process.cwd();
// Skip empty and invalid entries
if (!util.isString(path)) {
throw new TypeError('Arguments to path.resolve must be strings');
} else if (!path) {
continue;
}
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path.charAt(0) === '/';
}
// At this point the path should be resolved to a full absolute path, but
// handle relative paths to be safe (might happen when process.cwd() fails)
// Normalize the path
resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
return !!p;
}), !resolvedAbsolute).join('/');
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
};
// path.normalize(path)
// posix version
exports.normalize = function(path) {
var isAbsolute = exports.isAbsolute(path),
trailingSlash = path[path.length - 1] === '/',
segments = path.split('/'),
nonEmptySegments = [];
// Normalize the path
for (var i = 0; i < segments.length; i++) {
if (segments[i]) {
nonEmptySegments.push(segments[i]);
}
}
path = normalizeArray(nonEmptySegments, !isAbsolute).join('/');
if (!path && !isAbsolute) {
path = '.';
}
if (path && trailingSlash) {
path += '/';
}
return (isAbsolute ? '/' : '') + path;
};
// posix version
exports.isAbsolute = function(path) {
return path.charAt(0) === '/';
};
// posix version
exports.join = function() {
var path = '';
for (var i = 0; i < arguments.length; i++) {
var segment = arguments[i];
if (!util.isString(segment)) {
throw new TypeError('Arguments to path.join must be strings');
}
if (segment) {
if (!path) {
path += segment;
} else {
path += '/' + segment;
}
}
}
return exports.normalize(path);
};
// path.relative(from, to)
// posix version
exports.relative = function(from, to) {
from = exports.resolve(from).substr(1);
to = exports.resolve(to).substr(1);
function trim(arr) {
var start = 0;
for (; start < arr.length; start++) {
if (arr[start] !== '') break;
}
var end = arr.length - 1;
for (; end >= 0; end--) {
if (arr[end] !== '') break;
}
if (start > end) return [];
return arr.slice(start, end + 1);
}
var fromParts = trim(from.split('/'));
var toParts = trim(to.split('/'));
var length = Math.min(fromParts.length, toParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (fromParts[i] !== toParts[i]) {
samePartsLength = i;
break;
}
}
var outputParts = [];
for (var i = samePartsLength; i < fromParts.length; i++) {
outputParts.push('..');
}
outputParts = outputParts.concat(toParts.slice(samePartsLength));
return outputParts.join('/');
};
exports.sep = '/';
exports.delimiter = ':';
}
exports.dirname = function(path) {
var result = splitPath(path),
root = result[0],
dir = result[1];
if (!root && !dir) {
// No dirname whatsoever
return '.';
}
if (dir) {
// It has a dirname, strip trailing slash
dir = dir.substr(0, dir.length - 1);
}
return root + dir;
};
exports.basename = function(path, ext) {
var f = splitPath(path)[2];
// TODO: make this comparison case-insensitive on windows?
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
return f;
};
exports.extname = function(path) {
return splitPath(path)[3];
};
exports.exists = util.deprecate(function(path, callback) {
require('fs').exists(path, callback);
}, 'path.exists is now called `fs.exists`.');
exports.existsSync = util.deprecate(function(path) {
return require('fs').existsSync(path);
}, 'path.existsSync is now called `fs.existsSync`.');
if (isWindows) {
exports._makeLong = function(path) {
// Note: this will *probably* throw somewhere.
if (!util.isString(path))
return path;
if (!path) {
return '';
}
var resolvedPath = exports.resolve(path);
if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
// path is local filesystem path, which needs to be converted
// to long UNC path.
return '\\\\?\\' + resolvedPath;
} else if (/^\\\\[^?.]/.test(resolvedPath)) {
// path is network UNC path, which needs to be converted
// to long UNC path.
return '\\\\?\\UNC\\' + resolvedPath.substring(2);
}
return path;
};
} else {
exports._makeLong = function(path) {
return path;
};
}
exports.posix = exports;

View File

@ -1,4 +0,0 @@
import { Module } from "@coder/protocol";
import { client } from "./client";
export = client.modules[Module.Trash].trash;

View File

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

View File

@ -1,8 +0,0 @@
<!DOCTYPE html>
<html lang="en" style="width: 100%; height: 100%">
<head>
<title>Virtual Document</title>
</head>
<body style="margin: 0; overflow: hidden; width: 100%; height: 100%">
</body>
</html>

View File

@ -1,5 +0,0 @@
export * from "./client";
export * from "./fill/clipboard";
export * from "./fill/notification";
export * from "./retry";
export * from "./upload";

View File

@ -1,353 +0,0 @@
import { logger, field } from "@coder/logger";
import { NotificationService, INotificationHandle, INotificationService, Severity } from "./fill/notification";
// tslint:disable no-any can have different return values
interface IRetryItem {
/**
* How many times this item has been retried.
*/
count?: number;
/**
* In seconds.
*/
delay?: number;
/**
* In milliseconds.
*/
end?: number;
/**
* Function to run when retrying.
*/
fn(): any;
/**
* Timer for running this item.
*/
timeout?: number | NodeJS.Timer;
/**
* Whether the item is retrying or waiting to retry.
*/
running?: boolean;
}
/**
* An retry-able instance.
*/
export interface RetryInstance {
/**
* Run this retry.
*/
run(error?: Error): void;
/**
* Block on this instance.
*/
block(): void;
}
/**
* A retry-able instance that doesn't use a promise so it must be manually
* ran again on failure and recovered on success.
*/
export interface ManualRetryInstance extends RetryInstance {
/**
* Mark this item as recovered.
*/
recover(): void;
}
/**
* Retry services. Handles multiple services so when a connection drops the
* user doesn't get a separate notification for each service.
*
* Attempts to restart services silently up to a maximum number of tries, then
* starts waiting for a delay that grows exponentially with each attempt with a
* cap on the delay. Once the delay is long enough, it will show a notification
* to the user explaining what is happening with an option to immediately retry.
*/
export class Retry {
private readonly items = new Map<string, IRetryItem>();
// Times are in seconds.
private readonly retryMinDelay = 1;
private readonly retryMaxDelay = 3;
private readonly maxImmediateRetries = 5;
private readonly retryExponent = 1.5;
private blocked: string | boolean | undefined;
private notificationHandle: INotificationHandle | undefined;
private readonly updateDelay = 1;
private updateTimeout: number | NodeJS.Timer | undefined;
private readonly notificationThreshold = 3;
// Time in milliseconds to wait before restarting a service. (See usage below
// for reasoning.)
private readonly waitDelay = 50;
public constructor(private _notificationService: INotificationService) {}
public set notificationService(service: INotificationService) {
this._notificationService = service;
}
public get notificationService(): INotificationService {
return this._notificationService;
}
/**
* Register a function to retry that starts/connects to a service.
*
* The service is automatically retried or recovered when the promise resolves
* or rejects. If the service dies after starting, it must be manually
* retried.
*/
public register(name: string, fn: () => Promise<any>): RetryInstance;
/**
* Register a function to retry that starts/connects to a service.
*
* Must manually retry if it fails to start again or dies after restarting and
* manually recover if it succeeds in starting again.
*/
public register(name: string, fn: () => any): ManualRetryInstance;
/**
* Register a function to retry that starts/connects to a service.
*/
public register(name: string, fn: () => any): RetryInstance | ManualRetryInstance {
if (this.items.has(name)) {
throw new Error(`"${name}" is already registered`);
}
this.items.set(name, { fn });
return {
block: (): void => this.block(name),
run: (error?: Error): void => this.run(name, error),
recover: (): void => this.recover(name),
};
}
/**
* Un-register a function to retry.
*/
public unregister(name: string): void {
if (!this.items.has(name)) {
throw new Error(`"${name}" is not registered`);
}
this.items.delete(name);
}
/**
* Block retries when we know they will fail (for example when the socket is
* down ). If a name is passed, that service will still be allowed to retry
* (unless we have already blocked).
*
* Blocking without a name will override a block with a name.
*/
public block(name?: string): void {
if (!this.blocked || !name) {
this.blocked = name || true;
this.items.forEach((item) => {
this.stopItem(item);
});
}
}
/**
* Unblock retries and run any that are pending.
*/
private unblock(): void {
this.blocked = false;
this.items.forEach((item, name) => {
if (item.running) {
this.runItem(name, item);
}
});
}
/**
* Retry a service.
*/
private run(name: string, error?: Error): void {
if (!this.items.has(name)) {
throw new Error(`"${name}" is not registered`);
}
const item = this.items.get(name)!;
if (item.running) {
throw new Error(`"${name}" is already retrying`);
}
item.running = true;
// This timeout is for the case when the connection drops; this allows time
// for the socket service to come in and block everything because some other
// services might make it here first and try to restart, which will fail.
setTimeout(() => {
if (this.blocked && this.blocked !== name) {
return;
}
if (!item.count || item.count < this.maxImmediateRetries) {
return this.runItem(name, item, error);
}
if (!item.delay) {
item.delay = this.retryMinDelay;
} else {
item.delay = Math.ceil(item.delay * this.retryExponent);
if (item.delay > this.retryMaxDelay) {
item.delay = this.retryMaxDelay;
}
}
logger.info(`Retrying ${name.toLowerCase()} in ${item.delay}s`, error && field("error", error.message));
const itemDelayMs = item.delay * 1000;
item.end = Date.now() + itemDelayMs;
item.timeout = setTimeout(() => this.runItem(name, item, error), itemDelayMs);
this.updateNotification();
}, this.waitDelay);
}
/**
* Reset a service after a successfully recovering.
*/
private recover(name: string): void {
if (!this.items.has(name)) {
throw new Error(`"${name}" is not registered`);
}
const item = this.items.get(name)!;
if (typeof item.timeout === "undefined" && !item.running && typeof item.count !== "undefined") {
logger.info(`Connected to ${name.toLowerCase()}`);
item.delay = undefined;
item.count = undefined;
}
}
/**
* Run an item.
*/
private runItem(name: string, item: IRetryItem, error?: Error): void {
if (!item.count) {
item.count = 1;
} else {
++item.count;
}
const retryCountText = item.count <= this.maxImmediateRetries
? `[${item.count}/${this.maxImmediateRetries}]`
: `[${item.count}]`;
logger.info(`Starting ${name.toLowerCase()} ${retryCountText}...`, error && field("error", error.message));
const endItem = (): void => {
this.stopItem(item);
item.running = false;
};
try {
const maybePromise = item.fn();
if (maybePromise instanceof Promise) {
maybePromise.then(() => {
endItem();
this.recover(name);
if (this.blocked === name) {
this.unblock();
}
}).catch((error) => {
endItem();
this.run(name, error);
});
} else {
endItem();
}
} catch (error) {
// Prevent an exception from causing the item to never run again.
endItem();
throw error;
}
}
/**
* Update, close, or show the notification.
*/
private updateNotification(): void {
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
clearTimeout(this.updateTimeout as any);
const now = Date.now();
const items = Array.from(this.items.entries()).filter(([_, item]) => {
return typeof item.end !== "undefined"
&& item.end > now
&& item.delay && item.delay >= this.notificationThreshold;
}).sort((a, b) => {
return a[1] < b[1] ? -1 : 1;
});
if (items.length === 0) {
if (this.notificationHandle) {
this.notificationHandle.close();
this.notificationHandle = undefined;
}
return;
}
const join = (arr: string[]): string => {
const last = arr.pop()!; // Assume length > 0.
return arr.length > 0 ? `${arr.join(", ")} and ${last}` : last;
};
const servicesStr = join(items.map(([name, _]) => name.toLowerCase()));
const message = `Lost connection to ${servicesStr}. Retrying in ${
join(items.map(([_, item]) => `${Math.ceil((item.end! - now) / 1000)}s`))
}.`;
const buttons = [{
label: `Retry ${items.length > 1 ? "Services" : items[0][0]} Now`,
run: (): void => {
logger.info(`Forcing ${servicesStr} to restart now`);
items.forEach(([name, item]) => {
this.runItem(name, item);
});
this.updateNotification();
},
}];
if (!this.notificationHandle) {
this.notificationHandle = this.notificationService.prompt(
Severity.Info,
message,
buttons,
() => {
this.notificationHandle = undefined;
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
clearTimeout(this.updateTimeout as any);
},
);
} else {
this.notificationHandle.updateMessage(message);
this.notificationHandle.updateButtons(buttons);
}
this.updateTimeout = setTimeout(() => this.updateNotification(), this.updateDelay * 1000);
}
/**
* Stop an item's timer.
*/
private stopItem(item: IRetryItem): void {
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
clearTimeout(item.timeout as any);
item.timeout = undefined;
item.end = undefined;
}
}
// Global instance so we can block other retries when retrying the main
// connection.
export const retry = new Retry(new NotificationService());

View File

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@ -1,4 +0,0 @@
src
tsconfig.build.json
webpack.config.js
yarn.lock

View File

@ -1,32 +0,0 @@
# Logger
Beautiful logging inspired by https://github.com/uber-go/zap.
- Built for node and the browser
- Zero dependencies
- Uses groups in the browser to reduce clutter
## Example Usage
```javascript
import { field, logger } from "@coder/logger";
logger.info("Loading container",
field("container_id", container.id_str),
field("organization_id", organization.id_str));
```
## Formatting
By default the logger uses a different formatter depending on whether it detects
it is running in the browser or not. A custom formatter can be set:
```javascript
import { logger, Formatter } from "@coder/logger";
class MyFormatter extends Formatter {
// implementation ...
}
logger.formatter = new MyFormatter();
```

View File

@ -1,16 +0,0 @@
{
"name": "@coder/logger",
"description": "Beautiful logging inspired by https://github.com/uber-go/zap.",
"scripts": {
"build": "tsc -p tsconfig.build.json && cp ./out/packages/logger/src/* ./out && rm -rf out/packages && ../../node_modules/.bin/webpack --config ./webpack.config.js",
"postinstall": "if [ ! -d out ];then npm run build; fi"
},
"version": "1.1.3",
"main": "out/main.js",
"types": "out/index.d.ts",
"author": "Coder",
"license": "MIT",
"dependencies": {
"@google-cloud/logging": "^4.5.2"
}
}

View File

@ -1,12 +0,0 @@
import { field, logger } from "./logger";
import { createStackdriverExtender } from "./extender";
describe("Extender", () => {
it("should add stackdriver extender", () => {
logger.extend(createStackdriverExtender("coder-dev-1", "logging-package-tests"));
});
it("should log", async () => {
logger.debug("Bananas!", field("frog", { hi: "wow" }));
});
});

View File

@ -1,63 +0,0 @@
import * as gcl from "@google-cloud/logging";
import { Extender, logger, field } from "./logger";
export const createStackdriverExtender = (projectId: string, logId: string): Extender => {
enum GcpLogSeverity {
DEFAULT = 0,
DEBUG = 100,
INFO = 200,
NOTICE = 300,
WARNING = 400,
ERROR = 500,
CRITICAL = 600,
ALERT = 700,
EMERGENCY = 800,
}
const logging = new gcl.Logging({
autoRetry: true,
projectId,
});
const log = logging.log(logId);
const convertSeverity = (severity: "trace" | "info" | "warn" | "debug" | "error"): GcpLogSeverity => {
switch (severity) {
case "trace":
case "debug":
return GcpLogSeverity.DEBUG;
case "info":
return GcpLogSeverity.INFO;
case "error":
return GcpLogSeverity.ERROR;
case "warn":
return GcpLogSeverity.WARNING;
}
};
return (options): void => {
const severity = convertSeverity(options.type);
// tslint:disable-next-line:no-any
const metadata = {} as any;
if (options.fields) {
options.fields.forEach((f) => {
if (!f) {
return;
}
metadata[f.identifier] = f.value;
});
}
const entry = log.entry({
// tslint:disable-next-line:no-any
severity: severity as any,
}, {
...metadata,
message: options.message,
});
log.write(entry).catch((ex) => {
logger.named("GCP").error("Failed to log", field("error", ex));
});
};
};

View File

@ -1 +0,0 @@
export * from "./logger";

View File

@ -1,16 +0,0 @@
import { field, logger, BrowserFormatter, Time } from "./logger";
describe("Logger", () => {
it("should use server formatter", () => {
logger.info("test", field("key", "value"), field("time", new Time(100, Date.now())));
logger.named("name").debug("test name");
logger.named("another name").warn("another test name");
});
it("should use browser formatter", () => {
logger.formatter = new BrowserFormatter();
logger.info("test", field("key", "value"), field("time", new Time(100, Date.now())));
logger.named("name").debug("test name");
logger.named("another name").warn("another test name");
});
});

View File

@ -1,464 +0,0 @@
/**
* Log level.
*/
export enum Level {
Trace,
Debug,
Info,
Warning,
Error,
}
/**
* A field to log.
*/
export class Field<T> {
public constructor(
public readonly identifier: string,
public readonly value: T,
) { }
/**
* Convert field to JSON.
*/
public toJSON(): object {
return {
identifier: this.identifier,
value: this.value,
};
}
}
/**
* Represents the time something takes.
*/
export class Time {
public constructor(
public readonly expected: number,
public readonly ms: number,
) { }
}
// `undefined` is allowed to make it easy to conditionally display a field.
// For example: `error && field("error", error)`
// tslint:disable-next-line no-any
export type FieldArray = Array<Field<any> | undefined>;
// Functions can be used to remove the need to perform operations when the
// logging level won't output the result anyway.
export type LogCallback = () => [string, ...FieldArray];
/**
* Creates a time field
*/
export const time = (expected: number): Time => {
return new Time(expected, Date.now());
};
export const field = <T>(name: string, value: T): Field<T> => {
return new Field(name, value);
};
export type Extender = (msg: {
message: string,
level: Level,
type: "trace" | "info" | "warn" | "debug" | "error",
fields?: FieldArray,
section?: string,
}) => void;
/**
* This formats & builds text for logging.
* It should only be used to build one log item at a time since it stores the
* currently built items and appends to that.
*/
export abstract class Formatter {
protected format = "";
protected args = <string[]>[];
/**
* Add a tag.
*/
public abstract tag(name: string, color: string): void;
/**
* Add string or arbitrary variable.
*/
public abstract push(arg: string, color?: string, weight?: string): void;
public abstract push(arg: any): void; // tslint:disable-line no-any
// tslint:disable-next-line no-any
public abstract fields(fields: Array<Field<any>>): void;
/**
* Flush out the built arguments.
*/
public flush(): any[] { // tslint:disable-line no-any
const args = [this.format, ...this.args];
this.format = "";
this.args = [];
return args;
}
/**
* Get the format string for the value type.
*/
protected getType(arg: any): string { // tslint:disable-line no-any
switch (typeof arg) {
case "object":
return "%o";
case "number":
return "%d";
default:
return "%s";
}
}
}
/**
* Browser formatter.
*/
export class BrowserFormatter extends Formatter {
public tag(name: string, color: string): void {
this.format += `%c ${name} `;
this.args.push(
`border: 1px solid #222; background-color: ${color}; padding-top: 1px;`
+ " padding-bottom: 1px; font-size: 12px; font-weight: bold; color: white;"
+ (name.length === 4 ? "padding-left: 3px; padding-right: 4px;" : ""),
);
// A space to separate the tag from the title.
this.push(" ");
}
public push(arg: any, color: string = "inherit", weight: string = "normal"): void { // tslint:disable-line no-any
if (color || weight) {
this.format += "%c";
this.args.push(
(color ? `color: ${color};` : "") +
(weight ? `font-weight: ${weight};` : ""),
);
}
this.format += this.getType(arg);
this.args.push(arg);
}
// tslint:disable-next-line no-any
public fields(fields: Array<Field<any>>): void {
// tslint:disable-next-line no-console
console.groupCollapsed(...this.flush());
fields.forEach((field) => {
this.push(field.identifier, "#3794ff", "bold");
if (typeof field.value !== "undefined" && field.value.constructor && field.value.constructor.name) {
this.push(` (${field.value.constructor.name})`);
}
this.push(": ");
this.push(field.value);
// tslint:disable-next-line no-console
console.log(...this.flush());
});
// tslint:disable-next-line no-console
console.groupEnd();
}
}
/**
* Server (Node) formatter.
*/
export class ServerFormatter extends Formatter {
public tag(name: string, color: string): void {
const [r, g, b] = this.hexToRgb(color);
while (name.length < 5) {
name += " ";
}
this.format += "\u001B[1m";
this.format += `\u001B[38;2;${r};${g};${b}m${name} \u001B[0m`;
}
public push(arg: any, color?: string, weight?: string): void { // tslint:disable-line no-any
if (weight === "bold") {
this.format += "\u001B[1m";
}
if (color) {
const [r, g, b] = this.hexToRgb(color);
this.format += `\u001B[38;2;${r};${g};${b}m`;
}
this.format += this.getType(arg);
if (weight || color) {
this.format += "\u001B[0m";
}
this.args.push(arg);
}
// tslint:disable-next-line no-any
public fields(fields: Array<Field<any>>): void {
// tslint:disable-next-line no-any
const obj: { [key: string]: any} = {};
this.format += "\u001B[38;2;140;140;140m";
fields.forEach((field) => {
obj[field.identifier] = field.value;
});
this.args.push(JSON.stringify(obj));
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,
];
}
}
/**
* Class for logging.
*/
export class Logger {
public level = Level.Info;
private readonly nameColor?: string;
private muted: boolean = false;
public constructor(
private _formatter: Formatter,
private readonly name?: string,
private readonly defaultFields?: FieldArray,
private readonly extenders: Extender[] = [],
) {
if (name) {
this.nameColor = this.hashStringToColor(name);
}
const envLevel = typeof global !== "undefined" && typeof global.process !== "undefined" ? global.process.env.LOG_LEVEL : process.env.LOG_LEVEL;
if (envLevel) {
switch (envLevel) {
case "trace": this.level = Level.Trace; break;
case "debug": this.level = Level.Debug; break;
case "info": this.level = Level.Info; break;
case "warn": this.level = Level.Warning; break;
case "error": this.level = Level.Error; break;
}
}
}
public set formatter(formatter: Formatter) {
this._formatter = formatter;
}
/**
* Supresses all output
*/
public mute(): void {
this.muted = true;
}
public extend(extender: Extender): void {
this.extenders.push(extender);
}
/**
* Outputs information.
*/
public info(fn: LogCallback): void;
public info(message: string, ...fields: FieldArray): void;
public info(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
type: "info",
message,
fields,
tagColor: "#008FBF",
level: Level.Info,
});
}
/**
* Outputs a warning.
*/
public warn(fn: LogCallback): void;
public warn(message: string, ...fields: FieldArray): void;
public warn(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
type: "warn",
message,
fields,
tagColor: "#FF9D00",
level: Level.Warning,
});
}
/**
* Outputs a trace message.
*/
public trace(fn: LogCallback): void;
public trace(message: string, ...fields: FieldArray): void;
public trace(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
type: "trace",
message,
fields,
tagColor: "#888888",
level: Level.Trace,
});
}
/**
* Outputs a debug message.
*/
public debug(fn: LogCallback): void;
public debug(message: string, ...fields: FieldArray): void;
public debug(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
type: "debug",
message,
fields,
tagColor: "#84009E",
level: Level.Debug,
});
}
/**
* Outputs an error.
*/
public error(fn: LogCallback): void;
public error(message: string, ...fields: FieldArray): void;
public error(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
type: "error",
message,
fields,
tagColor: "#B00000",
level: Level.Error,
});
}
/**
* Returns a sub-logger with a name.
* Each name is deterministically generated a color.
*/
public named(name: string, ...fields: FieldArray): Logger {
const l = new Logger(this._formatter, name, fields, this.extenders);
if (this.muted) {
l.mute();
}
return l;
}
/**
* Outputs a message.
*/
private handle(options: {
type: "trace" | "info" | "warn" | "debug" | "error";
message: string | LogCallback;
fields?: FieldArray;
level: Level;
tagColor: string;
}): void {
if (this.level > options.level || this.muted) {
return;
}
let passedFields = options.fields || [];
if (typeof options.message === "function") {
const values = options.message();
options.message = values.shift() as string;
passedFields = values as FieldArray;
}
const fields = (this.defaultFields
? passedFields.filter((f) => !!f).concat(this.defaultFields)
: passedFields.filter((f) => !!f)) as Array<Field<any>>; // tslint:disable-line no-any
const now = Date.now();
let times: Array<Field<Time>> = [];
const hasFields = fields && fields.length > 0;
if (hasFields) {
times = fields.filter((f) => f.value instanceof Time);
}
this._formatter.tag(options.type.toUpperCase(), options.tagColor);
if (this.name && this.nameColor) {
this._formatter.tag(this.name.toUpperCase(), this.nameColor);
}
this._formatter.push(options.message);
if (times.length > 0) {
times.forEach((time) => {
const diff = now - time.value.ms;
const expPer = diff / time.value.expected;
const min = 125 * (1 - expPer);
const max = 125 + min;
const green = expPer < 1 ? max : min;
const red = expPer >= 1 ? max : min;
this._formatter.push(` ${time.identifier}=`, "#3794ff");
this._formatter.push(`${diff}ms`, this.rgbToHex(red > 0 ? red : 0, green > 0 ? green : 0, 0));
});
}
// tslint:disable no-console
if (hasFields) {
this._formatter.fields(fields);
} else {
console.log(...this._formatter.flush());
}
// tslint:enable no-console
this.extenders.forEach((extender) => {
extender({
section: this.name,
fields: options.fields,
level: options.level,
message: options.message as string,
type: options.type,
});
});
}
/**
* 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(
typeof process === "undefined" || typeof process.stdout === "undefined"
? new BrowserFormatter()
: new ServerFormatter(),
);

View File

@ -1,8 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"declarationDir": "out",
"declaration": true,
"emitDeclarationOnly": true
}
}

View File

@ -1,34 +0,0 @@
const path = require("path");
const merge = require("webpack-merge");
module.exports = [
merge(require(path.join(__dirname, "../../scripts", "webpack.general.config.js"))(), {
devtool: "none",
mode: "production",
target: "node",
output: {
path: path.join(__dirname, "out"),
filename: "main.js",
libraryTarget: "commonjs",
},
entry: [
"./packages/logger/src/index.ts"
],
}),
merge(require(path.join(__dirname, "../../scripts", "webpack.general.config.js"))(), {
devtool: "none",
mode: "production",
target: "node",
output: {
path: path.join(__dirname, "out"),
filename: "extender.js",
libraryTarget: "commonjs",
},
externals: {
"@google-cloud/logging": "commonjs @google-cloud/logging",
},
entry: [
"./packages/logger/src/extender.ts"
],
}),
];

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
{
"scripts": {
"postinstall": "../node_modules/.bin/ts-node ../scripts/install-packages.ts",
"test": "jest"
},
"devDependencies": {
"@types/jest": "^23.3.12",
"jest": "^23.6.0",
"ts-jest": "^23.10.5"
},
"dependencies": {
"xmlhttprequest": "1.8.0"
},
"jest": {
"globals": {
"ts-jest": {
"diagnostics": false
}
},
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
],
"setupFiles": [
"<rootDir>/../scripts/test-setup.js"
],
"moduleNameMapper": {
"^.+\\.(s?css|png|svg)$": "<rootDir>/../scripts/dummy.js",
"@coder/ide/src/fill/evaluation": "<rootDir>/ide/src/fill/evaluation",
"@coder/ide/src/fill/client": "<rootDir>/ide/src/fill/client",
"@coder/(.*)/test": "<rootDir>/$1/test",
"@coder/(.*)": "<rootDir>/$1/src",
"vs/(.*)": "<rootDir>/../lib/vscode/src/vs/$1",
"vszip": "<rootDir>/../lib/vscode/src/vs/base/node/zip.ts"
},
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testPathIgnorePatterns": [
"/node_modules/",
"/logger/"
],
"testRegex": ".*\\.test\\.tsx?"
}
}

View File

@ -1,47 +0,0 @@
# Protocol
This module provides a way for the browser to run Node modules like `fs`, `net`,
etc.
## Internals
### Server-side proxies
The server-side proxies are regular classes that call native Node functions. The
only thing special about them is that they must return promises and they must
return serializable values.
The only exception to the promise rule are event-related methods such as
`onEvent` and `onDone` (these are synchronous). The server will simply
immediately bind and push all events it can to the client. It doesn't wait for
the client to start listening. This prevents issues with the server not
receiving the client's request to start listening in time.
However, there is a way to specify events that should not bind immediately and
should wait for the client to request it, because some events (like `data` on a
stream) cannot be bound immediately (because doing so changes how the stream
behaves).
### Client-side proxies
Client-side proxies are `Proxy` instances. They simply make remote calls for any
method you call on it. The only exception is for events. Each client proxy has a
local emitter which it uses in place of a remote call (this allows the call to
be completed synchronously on the client). Then when an event is received from
the server, it gets emitted on that local emitter.
When an event is listened to, the proxy also notifies the server so it can start
listening in case it isn't already (see the `data` example above). This only
works for events that only fire after they are bound.
### Client-side fills
The client-side fills implement the Node API and make calls to the server-side
proxies using the client-side proxies.
When a proxy returns a proxy (for example `fs.createWriteStream`), that proxy is
a promise (since communicating with the server is asynchronous). We have to
return the fill from `fs.createWriteStream` synchronously, so that means the
fill has to contain a proxy promise. To eliminate the need for calling `then`
and to keep the code looking clean every time you use the proxy, the proxy is
itself wrapped in another proxy which just calls the method after a `then`. This
works since all the methods return promises (aside from the event methods, but
those are not used by the fills directly—they are only used internally to
forward events to the fill if it is an event emitter).

View File

@ -1,21 +0,0 @@
{
"name": "@coder/protocol",
"main": "src/index.ts",
"dependencies": {
"express": "^4.16.4",
"google-protobuf": "^3.6.1",
"trash": "^4.3.0",
"ws": "^6.1.2"
},
"devDependencies": {
"@types/google-protobuf": "^3.2.7",
"@types/rimraf": "^2.0.2",
"@types/text-encoding": "^0.0.35",
"rimraf": "^2.6.3",
"text-encoding": "^0.7.0",
"ts-protoc-gen": "^0.8.0"
},
"scripts": {
"gen": "./scripts/generate_proto.sh"
}
}

View File

@ -1,5 +0,0 @@
#!/bin/bash
cd "$(dirname "$0")/.."
protoc --plugin="protoc-gen-ts=./node_modules/.bin/protoc-gen-ts" --js_out="import_style=commonjs,binary:./src/proto" --ts_out="./src/proto" ./src/proto/*.proto --proto_path="./src/proto"

View File

@ -1,545 +0,0 @@
import { PathLike } from "fs";
import { ExecException, ExecOptions } from "child_process";
import { promisify } from "util";
import { Emitter } from "@coder/events";
import { logger, field } from "@coder/logger";
import { ReadWriteConnection, InitData, SharedProcessData } from "../common/connection";
import { ClientServerProxy, Module, ServerProxy } from "../common/proxy";
import { argumentToProto, protoToArgument, moduleToProto, protoToModule, protoToOperatingSystem } from "../common/util";
import { Argument, Ping, ServerMessage, ClientMessage, Method, Event, Callback } from "../proto";
import { FsModule, ChildProcessModule, NetModule, NodePtyModule, SpdlogModule, TrashModule } from "./modules";
// tslint:disable no-any
interface ProxyData {
promise: Promise<void>;
instance: any;
callbacks: Map<number, (...args: any[]) => void>;
}
/**
* Client accepts a connection to communicate with the server.
*/
export class Client {
private messageId = 0;
private callbackId = 0;
private readonly proxies = new Map<number | Module, ProxyData>();
private readonly successEmitter = new Emitter<Method.Success>();
private readonly failEmitter = new Emitter<Method.Fail>();
private readonly eventEmitter = new Emitter<{ event: string; args: any[]; }>();
private _initData: InitData | undefined;
private readonly initDataEmitter = new Emitter<InitData>();
private readonly initDataPromise: Promise<InitData>;
private readonly sharedProcessActiveEmitter = new Emitter<SharedProcessData>();
public readonly onSharedProcessActive = this.sharedProcessActiveEmitter.event;
private disconnected: boolean = false;
// The socket timeout is 60s, so we need to send a ping periodically to
// prevent it from closing.
private pingTimeout: NodeJS.Timer | number | undefined;
private readonly pingTimeoutDelay = 30000;
private readonly responseTimeout = 10000;
public readonly modules: {
[Module.ChildProcess]: ChildProcessModule,
[Module.Fs]: FsModule,
[Module.Net]: NetModule,
[Module.NodePty]: NodePtyModule,
[Module.Spdlog]: SpdlogModule,
[Module.Trash]: TrashModule,
};
/**
* @param connection Established connection to the server
*/
public constructor(private readonly connection: ReadWriteConnection) {
connection.onMessage(async (data) => {
let message: ServerMessage | undefined;
try {
message = ServerMessage.deserializeBinary(data);
await this.handleMessage(message);
} catch (error) {
logger.error(
"Failed to handle server message",
field("id", message && this.getMessageId(message)),
field("length", data.byteLength),
field("error", error.message),
);
}
});
this.createProxy(Module.ChildProcess);
this.createProxy(Module.Fs);
this.createProxy(Module.Net);
this.createProxy(Module.NodePty);
this.createProxy(Module.Spdlog);
this.createProxy(Module.Trash);
this.modules = {
[Module.ChildProcess]: new ChildProcessModule(this.getProxy(Module.ChildProcess).instance),
[Module.Fs]: new FsModule(this.getProxy(Module.Fs).instance),
[Module.Net]: new NetModule(this.getProxy(Module.Net).instance),
[Module.NodePty]: new NodePtyModule(this.getProxy(Module.NodePty).instance),
[Module.Spdlog]: new SpdlogModule(this.getProxy(Module.Spdlog).instance),
[Module.Trash]: new TrashModule(this.getProxy(Module.Trash).instance),
};
// Methods that don't follow the standard callback pattern (an error
// followed by a single result) need to provide a custom promisify function.
Object.defineProperty(this.modules[Module.Fs].exists, promisify.custom, {
value: (path: PathLike): Promise<boolean> => {
return new Promise((resolve): void => this.modules[Module.Fs].exists(path, resolve));
},
});
Object.defineProperty(this.modules[Module.ChildProcess].exec, promisify.custom, {
value: (
command: string,
options?: { encoding?: string | null } & ExecOptions | null,
): Promise<{ stdout: string | Buffer, stderr: string | Buffer }> => {
return new Promise((resolve, reject): void => {
this.modules[Module.ChildProcess].exec(command, options, (error: ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => {
if (error) {
reject(error);
} else {
resolve({ stdout, stderr });
}
});
});
},
});
/**
* If the connection is interrupted, the calls will neither succeed nor fail
* nor exit so we need to send a failure on all of them as well as trigger
* events so things like child processes can clean up and possibly restart.
*/
const handleDisconnect = (): void => {
this.disconnected = true;
logger.trace(() => [
"disconnected from server",
field("proxies", this.proxies.size),
field("callbacks", Array.from(this.proxies.values()).reduce((count, p) => count + p.callbacks.size, 0)),
field("success listeners", this.successEmitter.counts),
field("fail listeners", this.failEmitter.counts),
field("event listeners", this.eventEmitter.counts),
]);
const message = new Method.Fail();
const error = new Error("disconnected");
message.setResponse(argumentToProto(error));
this.failEmitter.emit(message);
this.eventEmitter.emit({ event: "disconnected", args: [error] });
this.eventEmitter.emit({ event: "done", args: [] });
};
connection.onDown(() => handleDisconnect());
connection.onClose(() => {
clearTimeout(this.pingTimeout as any);
this.pingTimeout = undefined;
handleDisconnect();
this.proxies.clear();
this.successEmitter.dispose();
this.failEmitter.dispose();
this.eventEmitter.dispose();
this.initDataEmitter.dispose();
this.sharedProcessActiveEmitter.dispose();
});
connection.onUp(() => this.disconnected = false);
this.initDataPromise = new Promise((resolve): void => {
this.initDataEmitter.event(resolve);
});
this.startPinging();
}
/**
* Close the connection.
*/
public dispose(): void {
this.connection.close();
}
public get initData(): Promise<InitData> {
return this.initDataPromise;
}
/**
* Make a remote call for a proxy's method using proto.
*/
private remoteCall(proxyId: number | Module, method: string, args: any[]): Promise<any> {
if (typeof proxyId === "number" && (this.disconnected || !this.proxies.has(proxyId))) {
// Can assume killing or closing works because a disconnected proxy is
// disposed on the server's side, and a non-existent proxy has already
// been disposed.
switch (method) {
case "close":
case "kill":
return Promise.resolve();
}
return Promise.reject(
new Error(`Unable to call "${method}" on proxy ${proxyId}: disconnected`),
);
}
const message = new Method();
const id = this.messageId++;
let proxyMessage: Method.Named | Method.Numbered;
if (typeof proxyId === "string") {
proxyMessage = new Method.Named();
proxyMessage.setModule(moduleToProto(proxyId));
message.setNamedProxy(proxyMessage);
} else {
proxyMessage = new Method.Numbered();
proxyMessage.setProxyId(proxyId);
message.setNumberedProxy(proxyMessage);
}
proxyMessage.setId(id);
proxyMessage.setMethod(method);
const storeCallback = (cb: (...args: any[]) => void): number => {
const callbackId = this.callbackId++;
logger.trace(() => [
"storing callback",
field("proxyId", proxyId),
field("callbackId", callbackId),
]);
this.getProxy(proxyId).callbacks.set(callbackId, cb);
return callbackId;
};
logger.trace(() => [
"sending",
field("id", id),
field("proxyId", proxyId),
field("method", method),
]);
proxyMessage.setArgsList(args.map((a) => argumentToProto<ClientServerProxy>(
a,
storeCallback,
(p) => p.proxyId,
)));
const clientMessage = new ClientMessage();
clientMessage.setMethod(message);
this.connection.send(clientMessage.serializeBinary());
// The server will send back a fail or success message when the method
// has completed, so we listen for that based on the message's unique ID.
const promise = new Promise((resolve, reject): void => {
const dispose = (): void => {
d1.dispose();
d2.dispose();
clearTimeout(timeout as any);
};
const timeout = setTimeout(() => {
dispose();
reject(new Error("timed out"));
}, this.responseTimeout);
const d1 = this.successEmitter.event(id, (message) => {
dispose();
resolve(this.protoToArgument(message.getResponse(), promise));
});
const d2 = this.failEmitter.event(id, (message) => {
dispose();
reject(protoToArgument(message.getResponse()));
});
});
return promise;
}
/**
* Handle all messages from the server.
*/
private async handleMessage(message: ServerMessage): Promise<void> {
switch (message.getMsgCase()) {
case ServerMessage.MsgCase.INIT:
const init = message.getInit()!;
this._initData = {
dataDirectory: init.getDataDirectory(),
homeDirectory: init.getHomeDirectory(),
tmpDirectory: init.getTmpDirectory(),
workingDirectory: init.getWorkingDirectory(),
os: protoToOperatingSystem(init.getOperatingSystem()),
shell: init.getShell(),
extensionsDirectory: init.getExtensionsDirectory(),
builtInExtensionsDirectory: init.getBuiltinExtensionsDir(),
extraExtensionDirectories: init.getExtraExtensionDirectoriesList(),
extraBuiltinExtensionDirectories: init.getExtraBuiltinExtensionDirectoriesList(),
env: init.getEnvMap(),
};
this.initDataEmitter.emit(this._initData);
break;
case ServerMessage.MsgCase.SUCCESS:
this.emitSuccess(message.getSuccess()!);
break;
case ServerMessage.MsgCase.FAIL:
this.emitFail(message.getFail()!);
break;
case ServerMessage.MsgCase.EVENT:
await this.emitEvent(message.getEvent()!);
break;
case ServerMessage.MsgCase.CALLBACK:
await this.runCallback(message.getCallback()!);
break;
case ServerMessage.MsgCase.SHARED_PROCESS_ACTIVE:
const sharedProcessActiveMessage = message.getSharedProcessActive()!;
this.sharedProcessActiveEmitter.emit({
socketPath: sharedProcessActiveMessage.getSocketPath(),
logPath: sharedProcessActiveMessage.getLogPath(),
});
break;
case ServerMessage.MsgCase.PONG:
// Nothing to do since pings are on a timer rather than waiting for the
// next pong in case a message from either the client or server is dropped
// which would break the ping cycle.
break;
default:
throw new Error("unknown message type");
}
}
/**
* Convert message to a success event.
*/
private emitSuccess(message: Method.Success): void {
logger.trace(() => [
"received resolve",
field("id", message.getId()),
]);
this.successEmitter.emit(message.getId(), message);
}
/**
* Convert message to a fail event.
*/
private emitFail(message: Method.Fail): void {
logger.trace(() => [
"received reject",
field("id", message.getId()),
]);
this.failEmitter.emit(message.getId(), message);
}
/**
* Emit an event received from the server. We could send requests for "on" to
* the server and serialize functions using IDs, but doing it that way makes
* it possible to miss events depending on whether the server receives the
* request before it emits. Instead, emit all events from the server so all
* events are always caught on the client.
*/
private async emitEvent(message: Event): Promise<void> {
const eventMessage = message.getNamedEvent()! || message.getNumberedEvent()!;
const proxyId = message.getNamedEvent()
? protoToModule(message.getNamedEvent()!.getModule())
: message.getNumberedEvent()!.getProxyId();
const event = eventMessage.getEvent();
await this.ensureResolved(proxyId);
logger.trace(() => [
"received event",
field("proxyId", proxyId),
field("event", event),
]);
const args = eventMessage.getArgsList().map((a) => this.protoToArgument(a));
this.eventEmitter.emit(proxyId, { event, args });
}
/**
* Run a callback as requested by the server. Since we don't know when
* callbacks get garbage collected we dispose them only when the proxy
* disposes. That means they should only be used if they run for the lifetime
* of the proxy (like child_process.exec), otherwise we'll leak. They should
* also only be used when passed together with the method. If they are sent
* afterward, they may never be called due to timing issues.
*/
private async runCallback(message: Callback): Promise<void> {
const callbackMessage = message.getNamedCallback()! || message.getNumberedCallback()!;
const proxyId = message.getNamedCallback()
? protoToModule(message.getNamedCallback()!.getModule())
: message.getNumberedCallback()!.getProxyId();
const callbackId = callbackMessage.getCallbackId();
await this.ensureResolved(proxyId);
logger.trace(() => [
"running callback",
field("proxyId", proxyId),
field("callbackId", callbackId),
]);
const args = callbackMessage.getArgsList().map((a) => this.protoToArgument(a));
this.getProxy(proxyId).callbacks.get(callbackId)!(...args);
}
/**
* Start the ping loop. Does nothing if already pinging.
*/
private readonly startPinging = (): void => {
if (typeof this.pingTimeout !== "undefined") {
return;
}
const schedulePing = (): void => {
this.pingTimeout = setTimeout(() => {
const clientMsg = new ClientMessage();
clientMsg.setPing(new Ping());
this.connection.send(clientMsg.serializeBinary());
schedulePing();
}, this.pingTimeoutDelay);
};
schedulePing();
}
/**
* Return the message's ID if it has one or a string identifier. For logging
* errors with an ID to make the error more useful.
*/
private getMessageId(message: ServerMessage): number | string | undefined {
if (message.hasInit()) {
return "init";
} else if (message.hasSuccess()) {
return message.getSuccess()!.getId();
} else if (message.hasFail()) {
return message.getFail()!.getId();
} else if (message.hasEvent()) {
const eventMessage = message.getEvent()!.getNamedEvent()!
|| message.getEvent()!.getNumberedEvent()!;
return `event: ${eventMessage.getEvent()}`;
} else if (message.hasCallback()) {
const callbackMessage = message.getCallback()!.getNamedCallback()!
|| message.getCallback()!.getNumberedCallback()!;
return `callback: ${callbackMessage.getCallbackId()}`;
} else if (message.hasSharedProcessActive()) {
return "shared";
} else if (message.hasPong()) {
return "pong";
}
}
/**
* Return a proxy that makes remote calls.
*/
private createProxy<T extends ClientServerProxy>(proxyId: number | Module, promise: Promise<any> = Promise.resolve()): T {
logger.trace(() => [
"creating proxy",
field("proxyId", proxyId),
]);
const instance = new Proxy({
proxyId,
onDone: (cb: (...args: any[]) => void): void => {
this.eventEmitter.event(proxyId, (event) => {
if (event.event === "done") {
cb(...event.args);
}
});
},
onEvent: (cb: (event: string, ...args: any[]) => void): void => {
this.eventEmitter.event(proxyId, (event) => {
cb(event.event, ...event.args);
});
},
} as ClientServerProxy, {
get: (target: any, name: string): any => {
// When resolving a promise with a proxy, it will check for "then".
if (name === "then") {
return;
}
if (typeof target[name] === "undefined") {
target[name] = (...args: any[]): Promise<any> | ServerProxy => {
return this.remoteCall(proxyId, name, args);
};
}
return target[name];
},
});
this.proxies.set(proxyId, {
promise,
instance,
callbacks: new Map(),
});
instance.onDone(() => {
const log = (): void => {
logger.trace(() => [
typeof proxyId === "number" ? "disposed proxy" : "disposed proxy callbacks",
field("proxyId", proxyId),
field("disconnected", this.disconnected),
field("callbacks", Array.from(this.proxies.values()).reduce((count, proxy) => count + proxy.callbacks.size, 0)),
field("success listeners", this.successEmitter.counts),
field("fail listeners", this.failEmitter.counts),
field("event listeners", this.eventEmitter.counts),
]);
};
// Uniquely identified items (top-level module proxies) can continue to
// be used so we don't need to delete them.
if (typeof proxyId === "number") {
const dispose = (): void => {
this.proxies.delete(proxyId);
this.eventEmitter.dispose(proxyId);
log();
};
if (!this.disconnected) {
instance.dispose().then(dispose).catch(dispose);
} else {
dispose();
}
} else {
// The callbacks will still be unusable though.
this.getProxy(proxyId).callbacks.clear();
log();
}
});
return instance;
}
/**
* We aren't guaranteed the promise will call all the `then` callbacks
* synchronously once it resolves, so the event message can come in and fire
* before a caller has been able to attach an event. Waiting for the promise
* ensures it runs after everything else.
*/
private async ensureResolved(proxyId: number | Module): Promise<void> {
await this.getProxy(proxyId).promise;
}
/**
* Same as protoToArgument except provides createProxy.
*/
private protoToArgument(value?: Argument, promise?: Promise<any>): any {
return protoToArgument(value, undefined, (id) => this.createProxy(id, promise));
}
/**
* Get a proxy. Error if it doesn't exist.
*/
private getProxy(proxyId: number | Module): ProxyData {
if (!this.proxies.has(proxyId)) {
throw new Error(`proxy ${proxyId} disposed too early`);
}
return this.proxies.get(proxyId)!;
}
}

View File

@ -1,151 +0,0 @@
import * as cp from "child_process";
import * as net from "net";
import * as stream from "stream";
import { callbackify } from "util";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { ChildProcessModuleProxy, ChildProcessProxy } from "../../node/modules/child_process";
import { ClientWritableProxy, ClientReadableProxy, Readable, Writable } from "./stream";
// tslint:disable completed-docs
export interface ClientChildProcessProxy extends ChildProcessProxy, ClientServerProxy<cp.ChildProcess> {}
export interface ClientChildProcessProxies {
childProcess: ClientChildProcessProxy;
stdin?: ClientWritableProxy | null;
stdout?: ClientReadableProxy | null;
stderr?: ClientReadableProxy | null;
}
export class ChildProcess extends ClientProxy<ClientChildProcessProxy> implements cp.ChildProcess {
public readonly stdin: stream.Writable;
public readonly stdout: stream.Readable;
public readonly stderr: stream.Readable;
public readonly stdio: [stream.Writable, stream.Readable, stream.Readable];
private _connected: boolean = false;
private _killed: boolean = false;
private _pid = -1;
public constructor(proxyPromises: Promise<ClientChildProcessProxies>) {
super(proxyPromises.then((p) => p.childProcess));
this.stdin = new Writable(proxyPromises.then((p) => p.stdin!));
this.stdout = new Readable(proxyPromises.then((p) => p.stdout!));
this.stderr = new Readable(proxyPromises.then((p) => p.stderr!));
this.stdio = [this.stdin, this.stdout, this.stderr];
this.catch(this.proxy.getPid().then((pid) => {
this._pid = pid;
this._connected = true;
}));
this.on("disconnect", () => this._connected = false);
this.on("exit", () => {
this._connected = false;
this._killed = true;
});
}
public get pid(): number {
return this._pid;
}
public get connected(): boolean {
return this._connected;
}
public get killed(): boolean {
return this._killed;
}
public kill(): void {
this._killed = true;
this.catch(this.proxy.kill());
}
public disconnect(): void {
this.catch(this.proxy.disconnect());
}
public ref(): void {
this.catch(this.proxy.ref());
}
public unref(): void {
this.catch(this.proxy.unref());
}
public send(
message: any, // tslint:disable-line no-any
sendHandle?: net.Socket | net.Server | ((error: Error) => void),
options?: cp.MessageOptions | ((error: Error) => void),
callback?: (error: Error) => void): boolean {
if (typeof sendHandle === "function") {
callback = sendHandle;
sendHandle = undefined;
} else if (typeof options === "function") {
callback = options;
options = undefined;
}
if (sendHandle || options) {
throw new Error("sendHandle and options are not supported");
}
callbackify(this.proxy.send)(message, (error) => {
if (callback) {
callback(error);
}
});
return true; // Always true since we can't get this synchronously.
}
/**
* Exit and close the process when disconnected.
*/
protected handleDisconnect(): void {
this.emit("exit", 1);
this.emit("close");
}
}
interface ClientChildProcessModuleProxy extends ChildProcessModuleProxy, ClientServerProxy {
exec(command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void)): Promise<ClientChildProcessProxies>;
fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ClientChildProcessProxies>;
spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ClientChildProcessProxies>;
}
export class ChildProcessModule {
public constructor(private readonly proxy: ClientChildProcessModuleProxy) {}
public exec = (
command: string,
options?: { encoding?: string | null } & cp.ExecOptions | null
| ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
callback?: ((error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void),
): cp.ChildProcess => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
return new ChildProcess(this.proxy.exec(command, options, callback));
}
public fork = (modulePath: string, args?: string[] | cp.ForkOptions, options?: cp.ForkOptions): cp.ChildProcess => {
if (!Array.isArray(args)) {
options = args;
args = undefined;
}
return new ChildProcess(this.proxy.fork(modulePath, args, options));
}
public spawn = (command: string, args?: string[] | cp.SpawnOptions, options?: cp.SpawnOptions): cp.ChildProcess => {
if (!Array.isArray(args)) {
options = args;
args = undefined;
}
return new ChildProcess(this.proxy.spawn(command, args, options));
}
}

View File

@ -1,380 +0,0 @@
import * as fs from "fs";
import { callbackify } from "util";
import { Batch, ClientProxy, ClientServerProxy } from "../../common/proxy";
import { IEncodingOptions, IEncodingOptionsCallback } from "../../common/util";
import { FsModuleProxy, ReadStreamProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
import { Readable, Writable } from "./stream";
// tslint:disable completed-docs no-any
class StatBatch extends Batch<IStats, { path: fs.PathLike }> {
public constructor(private readonly proxy: FsModuleProxy) {
super();
}
protected remoteCall(batch: { path: fs.PathLike }[]): Promise<(IStats | Error)[]> {
return this.proxy.statBatch(batch);
}
}
class LstatBatch extends Batch<IStats, { path: fs.PathLike }> {
public constructor(private readonly proxy: FsModuleProxy) {
super();
}
protected remoteCall(batch: { path: fs.PathLike }[]): Promise<(IStats | Error)[]> {
return this.proxy.lstatBatch(batch);
}
}
class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.PathLike, options: IEncodingOptions }> {
public constructor(private readonly proxy: FsModuleProxy) {
super();
}
protected remoteCall(queue: { path: fs.PathLike, options: IEncodingOptions }[]): Promise<(Buffer[] | fs.Dirent[] | string[] | Error)[]> {
return this.proxy.readdirBatch(queue);
}
}
interface ClientWatcherProxy extends WatcherProxy, ClientServerProxy<fs.FSWatcher> {}
class Watcher extends ClientProxy<ClientWatcherProxy> implements fs.FSWatcher {
public close(): void {
this.catch(this.proxy.close());
}
protected handleDisconnect(): void {
this.emit("close");
}
}
interface ClientReadStreamProxy extends ReadStreamProxy, ClientServerProxy<fs.ReadStream> {}
class ReadStream extends Readable<ClientReadStreamProxy> implements fs.ReadStream {
public get bytesRead(): number {
throw new Error("not implemented");
}
public get path(): string | Buffer {
throw new Error("not implemented");
}
public close(): void {
this.catch(this.proxy.close());
}
}
interface ClientWriteStreamProxy extends WriteStreamProxy, ClientServerProxy<fs.WriteStream> {}
class WriteStream extends Writable<ClientWriteStreamProxy> implements fs.WriteStream {
public get bytesWritten(): number {
throw new Error("not implemented");
}
public get path(): string | Buffer {
throw new Error("not implemented");
}
public close(): void {
this.catch(this.proxy.close());
}
}
interface ClientFsModuleProxy extends FsModuleProxy, ClientServerProxy {
createReadStream(path: fs.PathLike, options?: any): Promise<ClientReadStreamProxy>;
createWriteStream(path: fs.PathLike, options?: any): Promise<ClientWriteStreamProxy>;
watch(filename: fs.PathLike, options?: IEncodingOptions): Promise<ClientWatcherProxy>;
}
export class FsModule {
private readonly statBatch: StatBatch;
private readonly lstatBatch: LstatBatch;
private readonly readdirBatch: ReaddirBatch;
public constructor(private readonly proxy: ClientFsModuleProxy) {
this.statBatch = new StatBatch(this.proxy);
this.lstatBatch = new LstatBatch(this.proxy);
this.readdirBatch = new ReaddirBatch(this.proxy);
}
public access = (path: fs.PathLike, mode: number | undefined | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof mode === "function") {
callback = mode;
mode = undefined;
}
callbackify(this.proxy.access)(path, mode, callback!);
}
public appendFile = (path: fs.PathLike | number, data: any, options?: fs.WriteFileOptions | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.appendFile)(path, data, options, callback!);
}
public chmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.chmod)(path, mode, callback!);
}
public chown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.chown)(path, uid, gid, callback!);
}
public close = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.close)(fd, callback!);
}
public copyFile = (src: fs.PathLike, dest: fs.PathLike, flags: number | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof flags === "function") {
callback = flags;
}
callbackify(this.proxy.copyFile)(
src, dest, typeof flags !== "function" ? flags : undefined, callback!,
);
}
public createReadStream = (path: fs.PathLike, options?: any): fs.ReadStream => {
return new ReadStream(this.proxy.createReadStream(path, options));
}
public createWriteStream = (path: fs.PathLike, options?: any): fs.WriteStream => {
return new WriteStream(this.proxy.createWriteStream(path, options));
}
public exists = (path: fs.PathLike, callback: (exists: boolean) => void): void => {
this.proxy.exists(path).then((exists) => callback(exists)).catch(() => callback(false));
}
public fchmod = (fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fchmod)(fd, mode, callback!);
}
public fchown = (fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fchown)(fd, uid, gid, callback!);
}
public fdatasync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fdatasync)(fd, callback!);
}
public fstat = (fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
callbackify(this.proxy.fstat)(fd, (error, stats) => {
callback(error, stats && new Stats(stats));
});
}
public fsync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.fsync)(fd, callback!);
}
public ftruncate = (fd: number, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof len === "function") {
callback = len;
len = undefined;
}
callbackify(this.proxy.ftruncate)(fd, len, callback!);
}
public futimes = (fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.futimes)(fd, atime, mtime, callback!);
}
public lchmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.lchmod)(path, mode, callback!);
}
public lchown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.lchown)(path, uid, gid, callback!);
}
public link = (existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.link)(existingPath, newPath, callback!);
}
public lstat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
callbackify(this.lstatBatch.add)({ path }, (error, stats) => {
callback(error, stats && new Stats(stats));
});
}
public mkdir = (path: fs.PathLike, mode: number | string | fs.MakeDirectoryOptions | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof mode === "function") {
callback = mode;
mode = undefined;
}
callbackify(this.proxy.mkdir)(path, mode, callback!);
}
public mkdtemp = (prefix: string, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, folder: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.mkdtemp)(prefix, options, callback!);
}
public open = (path: fs.PathLike, flags: string | number, mode: string | number | undefined | null | ((err: NodeJS.ErrnoException, fd: number) => void), callback?: (err: NodeJS.ErrnoException, fd: number) => void): void => {
if (typeof mode === "function") {
callback = mode;
mode = undefined;
}
callbackify(this.proxy.open)(path, flags, mode, callback!);
}
public read = (fd: number, buffer: Buffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void => {
this.proxy.read(fd, length, position).then((response) => {
buffer.set(response.buffer, offset);
callback(undefined!, response.bytesRead, response.buffer);
}).catch((error) => {
callback(error, undefined!, undefined!);
});
}
public readFile = (path: fs.PathLike | number, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.readFile)(path, options, callback!);
}
public readdir = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, files: Buffer[] | fs.Dirent[] | string[]) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.readdirBatch.add)({ path, options }, callback!);
}
public readlink = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, linkString: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.readlink)(path, options, callback!);
}
public realpath = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, resolvedPath: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.realpath)(path, options, callback!);
}
public rename = (oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.rename)(oldPath, newPath, callback!);
}
public rmdir = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.rmdir)(path, callback!);
}
public stat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
callbackify(this.statBatch.add)({ path }, (error, stats) => {
callback(error, stats && new Stats(stats));
});
}
public symlink = (target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof type === "function") {
callback = type;
type = undefined;
}
callbackify(this.proxy.symlink)(target, path, type, callback!);
}
public truncate = (path: fs.PathLike, len: number | undefined | null | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof len === "function") {
callback = len;
len = undefined;
}
callbackify(this.proxy.truncate)(path, len, callback!);
}
public unlink = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.unlink)(path, callback!);
}
public utimes = (path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
callbackify(this.proxy.utimes)(path, atime, mtime, callback!);
}
public write = (fd: number, buffer: Buffer, offset: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), length: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), position: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void), callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void => {
if (typeof offset === "function") {
callback = offset;
offset = undefined;
}
if (typeof length === "function") {
callback = length;
length = undefined;
}
if (typeof position === "function") {
callback = position;
position = undefined;
}
this.proxy.write(fd, buffer, offset, length, position).then((r) => {
callback!(undefined!, r.bytesWritten, r.buffer);
}).catch((error) => {
callback!(error, undefined!, undefined!);
});
}
public writeFile = (path: fs.PathLike | number, data: any, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
callbackify(this.proxy.writeFile)(path, data, options, callback!);
}
public watch = (filename: fs.PathLike, options?: IEncodingOptions | ((event: string, filename: string | Buffer) => void), listener?: ((event: string, filename: string | Buffer) => void)): fs.FSWatcher => {
if (typeof options === "function") {
listener = options;
options = undefined;
}
const watcher = new Watcher(this.proxy.watch(filename, options));
if (listener) {
watcher.on("change", listener);
}
return watcher;
}
}
class Stats implements fs.Stats {
public constructor(private readonly stats: IStats) {}
public get dev(): number { return this.stats.dev; }
public get ino(): number { return this.stats.ino; }
public get mode(): number { return this.stats.mode; }
public get nlink(): number { return this.stats.nlink; }
public get uid(): number { return this.stats.uid; }
public get gid(): number { return this.stats.gid; }
public get rdev(): number { return this.stats.rdev; }
public get size(): number { return this.stats.size; }
public get blksize(): number { return this.stats.blksize; }
public get blocks(): number { return this.stats.blocks; }
public get atime(): Date { return this.stats.atime; }
public get mtime(): Date { return this.stats.mtime; }
public get ctime(): Date { return this.stats.ctime; }
public get birthtime(): Date { return this.stats.birthtime; }
public get atimeMs(): number { return this.stats.atimeMs; }
public get mtimeMs(): number { return this.stats.mtimeMs; }
public get ctimeMs(): number { return this.stats.ctimeMs; }
public get birthtimeMs(): number { return this.stats.birthtimeMs; }
public isFile(): boolean { return this.stats._isFile; }
public isDirectory(): boolean { return this.stats._isDirectory; }
public isBlockDevice(): boolean { return this.stats._isBlockDevice; }
public isCharacterDevice(): boolean { return this.stats._isCharacterDevice; }
public isSymbolicLink(): boolean { return this.stats._isSymbolicLink; }
public isFIFO(): boolean { return this.stats._isFIFO; }
public isSocket(): boolean { return this.stats._isSocket; }
public toObject(): object {
return JSON.parse(JSON.stringify(this));
}
}

View File

@ -1,6 +0,0 @@
export * from "./child_process";
export * from "./fs";
export * from "./net";
export * from "./node-pty";
export * from "./spdlog";
export * from "./trash";

View File

@ -1,296 +0,0 @@
import * as net from "net";
import { callbackify } from "util";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { NetModuleProxy, NetServerProxy, NetSocketProxy } from "../../node/modules/net";
import { Duplex } from "./stream";
// tslint:disable completed-docs
interface ClientNetSocketProxy extends NetSocketProxy, ClientServerProxy<net.Socket> {}
export class Socket extends Duplex<ClientNetSocketProxy> implements net.Socket {
private _connecting: boolean = false;
private _destroyed: boolean = false;
public constructor(proxyPromise: Promise<ClientNetSocketProxy> | ClientNetSocketProxy, connecting?: boolean) {
super(proxyPromise);
if (connecting) {
this._connecting = connecting;
}
this.on("close", () => {
this._destroyed = true;
this._connecting = false;
});
this.on("connect", () => this._connecting = false);
}
public connect(options: number | string | net.SocketConnectOpts, host?: string | Function, callback?: Function): this {
if (typeof host === "function") {
callback = host;
host = undefined;
}
this._connecting = true;
if (callback) {
this.on("connect", callback as () => void);
}
return this.catch(this.proxy.connect(options, host));
}
// tslint:disable-next-line no-any
public end(data?: any, encoding?: string | Function, callback?: Function): void {
if (typeof encoding === "function") {
callback = encoding;
encoding = undefined;
}
callbackify(this.proxy.end)(data, encoding, () => {
if (callback) {
callback();
}
});
}
// tslint:disable-next-line no-any
public write(data: any, encoding?: string | Function, fd?: string | Function): boolean {
let callback: undefined | Function;
if (typeof encoding === "function") {
callback = encoding;
encoding = undefined;
}
if (typeof fd === "function") {
callback = fd;
fd = undefined;
}
if (typeof fd !== "undefined") {
throw new Error("fd argument not supported");
}
callbackify(this.proxy.write)(data, encoding, () => {
if (callback) {
callback();
}
});
return true; // Always true since we can't get this synchronously.
}
public get connecting(): boolean {
return this._connecting;
}
public get destroyed(): boolean {
return this._destroyed;
}
public get bufferSize(): number {
throw new Error("not implemented");
}
public get bytesRead(): number {
throw new Error("not implemented");
}
public get bytesWritten(): number {
throw new Error("not implemented");
}
public get localAddress(): string {
throw new Error("not implemented");
}
public get localPort(): number {
throw new Error("not implemented");
}
public address(): net.AddressInfo | string {
throw new Error("not implemented");
}
public setTimeout(): this {
throw new Error("not implemented");
}
public setNoDelay(): this {
throw new Error("not implemented");
}
public setKeepAlive(): this {
throw new Error("not implemented");
}
public unref(): void {
this.catch(this.proxy.unref());
}
public ref(): void {
this.catch(this.proxy.ref());
}
}
interface ClientNetServerProxy extends NetServerProxy, ClientServerProxy<net.Server> {
onConnection(cb: (proxy: ClientNetSocketProxy) => void): Promise<void>;
}
export class Server extends ClientProxy<ClientNetServerProxy> implements net.Server {
private socketId = 0;
private readonly sockets = new Map<number, net.Socket>();
private _listening: boolean = false;
public constructor(proxyPromise: Promise<ClientNetServerProxy> | ClientNetServerProxy) {
super(proxyPromise);
this.catch(this.proxy.onConnection((socketProxy) => {
const socket = new Socket(socketProxy);
const socketId = this.socketId++;
this.sockets.set(socketId, socket);
socket.on("error", () => this.sockets.delete(socketId));
socket.on("close", () => this.sockets.delete(socketId));
this.emit("connection", socket);
}));
this.on("listening", () => this._listening = true);
this.on("error", () => this._listening = false);
this.on("close", () => this._listening = false);
}
public listen(handle?: net.ListenOptions | number | string, hostname?: string | number | Function, backlog?: number | Function, callback?: Function): this {
if (typeof hostname === "function") {
callback = hostname;
hostname = undefined;
}
if (typeof backlog === "function") {
callback = backlog;
backlog = undefined;
}
if (callback) {
this.on("listening", callback as () => void);
}
return this.catch(this.proxy.listen(handle, hostname, backlog));
}
public get connections(): number {
return this.sockets.size;
}
public get listening(): boolean {
return this._listening;
}
public get maxConnections(): number {
throw new Error("not implemented");
}
public address(): net.AddressInfo | string {
throw new Error("not implemented");
}
public close(callback?: () => void): this {
this._listening = false;
if (callback) {
this.on("close", callback);
}
return this.catch(this.proxy.close());
}
public ref(): this {
return this.catch(this.proxy.ref());
}
public unref(): this {
return this.catch(this.proxy.unref());
}
public getConnections(cb: (error: Error | null, count: number) => void): void {
cb(null, this.sockets.size);
}
protected handleDisconnect(): void {
this.emit("close");
}
}
type NodeNet = typeof net;
interface ClientNetModuleProxy extends NetModuleProxy, ClientServerProxy {
createSocket(options?: net.SocketConstructorOpts): Promise<ClientNetSocketProxy>;
createConnection(target: string | number | net.NetConnectOpts, host?: string): Promise<ClientNetSocketProxy>;
createServer(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean }): Promise<ClientNetServerProxy>;
}
export class NetModule implements NodeNet {
public readonly Socket: typeof net.Socket;
public readonly Server: typeof net.Server;
public constructor(private readonly proxy: ClientNetModuleProxy) {
// @ts-ignore this is because Socket is missing things from the Stream
// namespace but I'm unsure how best to provide them (finished,
// finished.__promisify__, pipeline, and some others) or if it even matters.
this.Socket = class extends Socket {
public constructor(options?: net.SocketConstructorOpts) {
super(proxy.createSocket(options));
}
};
this.Server = class extends Server {
public constructor(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: Socket) => void), listener?: (socket: Socket) => void) {
super(proxy.createServer(typeof options !== "function" ? options : undefined));
if (typeof options === "function") {
listener = options;
}
if (listener) {
this.on("connection", listener);
}
}
};
}
public createConnection = (target: string | number | net.NetConnectOpts, host?: string | Function, callback?: Function): net.Socket => {
if (typeof host === "function") {
callback = host;
host = undefined;
}
const socket = new Socket(this.proxy.createConnection(target, host), true);
if (callback) {
socket.on("connect", callback as () => void);
}
return socket;
}
public createServer = (
options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
callback?: (socket: net.Socket) => void,
): net.Server => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
const server = new Server(this.proxy.createServer(options));
if (callback) {
server.on("connection", callback);
}
return server;
}
public connect = (): 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");
}
}

View File

@ -1,79 +0,0 @@
import * as pty from "node-pty";
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
import { NodePtyModuleProxy, NodePtyProcessProxy } from "../../node/modules/node-pty";
// tslint:disable completed-docs
interface ClientNodePtyProcessProxy extends NodePtyProcessProxy, ClientServerProxy {}
export class NodePtyProcess extends ClientProxy<ClientNodePtyProcessProxy> implements pty.IPty {
private _pid = -1;
private _process = "";
private lastCols: number | undefined;
private lastRows: number | undefined;
public constructor(
private readonly moduleProxy: ClientNodePtyModuleProxy,
private readonly file: string,
private readonly args: string[] | string,
private readonly options: pty.IPtyForkOptions,
) {
super(moduleProxy.spawn(file, args, options));
this.on("process", (process) => this._process = process);
}
protected initialize(proxyPromise: Promise<ClientNodePtyProcessProxy>): ClientNodePtyProcessProxy {
const proxy = super.initialize(proxyPromise);
this.catch(this.proxy.getPid().then((p) => this._pid = p));
this.catch(this.proxy.getProcess().then((p) => this._process = p));
return proxy;
}
public get pid(): number {
return this._pid;
}
public get process(): string {
return this._process;
}
public resize(columns: number, rows: number): void {
this.lastCols = columns;
this.lastRows = rows;
this.catch(this.proxy.resize(columns, rows));
}
public write(data: string): void {
this.catch(this.proxy.write(data));
}
public kill(signal?: string): void {
this.catch(this.proxy.kill(signal));
}
protected handleDisconnect(): void {
this._process += " (disconnected)";
this.emit("data", "\r\n\nLost connection...\r\n\n");
this.initialize(this.moduleProxy.spawn(this.file, this.args, {
...this.options,
cols: this.lastCols || this.options.cols,
rows: this.lastRows || this.options.rows,
}));
}
}
type NodePty = typeof pty;
interface ClientNodePtyModuleProxy extends NodePtyModuleProxy, ClientServerProxy {
spawn(file: string, args: string[] | string, options: pty.IPtyForkOptions): Promise<ClientNodePtyProcessProxy>;
}
export class NodePtyModule implements NodePty {
public constructor(private readonly proxy: ClientNodePtyModuleProxy) {}
public spawn = (file: string, args: string[] | string, options: pty.IPtyForkOptions): pty.IPty => {
return new NodePtyProcess(this.proxy, file, args, options);
}
}

Some files were not shown because too many files have changed in this diff Show More