diff --git a/.gitignore b/.gitignore index 076f2ae7..444ef229 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ lib node_modules dist +out \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..8ce5e70e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: node_js +node_js: +- 8.9.3 +matrix: + include: + - os: linux + dist: ubuntu + - os: osx + - os: windows +before_install: +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install libxkbfile-dev libsecret-1-dev; + fi +script: +- scripts/build.sh +deploy: + provider: releases + skip_cleanup: true + api_key: + secure: T/yqCIeqLifteriv8D3CnehNFzSBP309GZqeUfyx8Q+xSQEttA9Enxl+Qw9GkdedYTN4J56iucHIac6CwcvKSKIXqk80CeSEW0BNxZs5wIgv4rRMMy/GAX0NBWKNOkoGlH8M6VyQcM7eY2iGAn1EX755PHalk6rWwfsauRANOQyb2DXQBan5C0YUnogq2qcW1xkIwlXH7l0Ekbtego0f6QPv0rSyOcL1LKm6xk0Aq+xLNKJkT6TSL6xYpkPlZLjnql09Nspkqs6NehWlft2n09bHqAtjNnWw9OYCvxp8mdHeTE5uShuEqYPzdYU5LVFoE7wElI8uqS66noaA18ytZYGw2IrY6GZcn+wtR6WyM2+YXl2HclL1/Fs6Vn8+zwq2IBZchBNv3KJSn1dxiqLlD/s6YQyni17x/9FhtFoNUvsbY5zSC1xrnNQBQWFg0TRnoC9rPR+7hQtT1+5+CvRxpvcNWnPuA22919PFE79ejJulPmsnyF+YLs9c6APJgOpOO1f6fKt5Mcb02dubPqGcQ9NbqUUNTl4IUvEtjG0LnFAgEGerxAcsdnUTxzBVf0LJLlhRKW1BigUTbRwfUJL1DN0mWg9cg7fL5VqrogvNq3uRguxOsYr+bcHDbimQSAY3No3fAkTTqQSJh56Dx57/Un18KxuOTiRB9de1RtiudsI= + file: packages/server/cli-$TRAVIS_OS_NAME + on: + repo: codercom/vscode-online + all_branches: true diff --git a/build/tasks.ts b/build/tasks.ts new file mode 100644 index 00000000..d8e55336 --- /dev/null +++ b/build/tasks.ts @@ -0,0 +1,195 @@ +import { register, run } from "@coder/runner"; +import * as fs from "fs"; +import * as fse from "fs-extra"; +import * as path from "path"; +import * as zlib from "zlib"; + +const libPath = path.join(__dirname, "../lib"); +const vscodePath = path.join(libPath, "vscode"); +const pkgsPath = path.join(__dirname, "../packages"); +const defaultExtensionsPath = path.join(libPath, "VSCode-linux-x64/resources/app/extensions"); + +const buildServerBinary = register("build:server:binary", async (runner) => { + await ensureInstalled(); + await copyForDefaultExtensions(); + await Promise.all([ + buildBootstrapFork(), + buildWeb(), + buildDefaultExtensions(), + 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 web bundle built"); + } + await buildServerBinaryCopy(); + const resp = await runner.execute("npm", ["run", "build:nexe"]); + 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", "bin", "bootstrap-fork.js"); + const webOutputPath = path.join(pkgsPath, "web", "out"); + const browserAppOutputPath = path.join(pkgsPath, "app", "browser", "out"); + const nodePtyModule = path.join(pkgsPath, "protocol", "node_modules", "node-pty", "build", "Release", "pty.node"); + + if (!fs.existsSync(nodePtyModule)) { + throw new Error("Could not find pty.node. Ensure all packages have been installed"); + } + 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"); + } + 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, subdir: "auth" | "unauth", rootPath: string): void => { + const stat = fs.statSync(dir); + if (stat.isDirectory()) { + const paths = fs.readdirSync(dir); + paths.forEach((p) => cpDir(path.join(dir, p), subdir, rootPath)); + } 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, "auth", webOutputPath); + cpDir(browserAppOutputPath, "unauth", browserAppOutputPath); + fse.mkdirpSync(path.join(cliBuildPath, "modules")); + fse.copySync(nodePtyModule, path.join(cliBuildPath, "modules", "pty.node")); +}); + +const buildServerBundle = register("build:server:bundle", async (runner) => { + const cliPath = path.join(pkgsPath, "server"); + runner.cwd = cliPath; + await runner.execute("npm", ["run", "build:webpack"]); +}); + +const buildBootstrapFork = register("build:bootstrap-fork", async (runner) => { + await ensureInstalled(); + await ensurePatched(); + + const vscodePkgPath = path.join(pkgsPath, "vscode"); + runner.cwd = vscodePkgPath; + await runner.execute("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("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("npm", ["run", "build"]); +}); + +const extDirPath = path.join("lib", "vscode-default-extensions"); +const copyForDefaultExtensions = register("build:copy-vscode", async (runner) => { + if (!fs.existsSync(defaultExtensionsPath)) { + await ensureClean(); + fse.removeSync(extDirPath); + fse.copySync(vscodePath, extDirPath); + } +}); + +const buildDefaultExtensions = register("build:default-extensions", async (runner) => { + if (!fs.existsSync(defaultExtensionsPath)) { + await copyForDefaultExtensions(); + runner.cwd = extDirPath; + const resp = await runner.execute("npx", ["gulp", "vscode-linux-x64"]); + if (resp.exitCode !== 0) { + throw new Error(`Failed to build default extensions: ${resp.stderr}`); + } + } +}); + +const ensureInstalled = register("vscode:install", async (runner) => { + await ensureCloned(); + + runner.cwd = vscodePath; + const install = await runner.execute("yarn", []); + if (install.exitCode !== 0) { + throw new Error(`Failed to install vscode dependencies: ${install.stderr}`); + } +}); + +const ensureCloned = register("vscode:clone", async (runner) => { + if (fs.existsSync(vscodePath)) { + await ensureClean(); + } else { + fs.mkdirSync(libPath); + runner.cwd = libPath; + const clone = await runner.execute("git", ["clone", "https://github.com/microsoft/vscode"]); + if (clone.exitCode !== 0) { + throw new Error(`Failed to clone: ${clone.exitCode}`); + } + } + + runner.cwd = vscodePath; + const checkout = await runner.execute("git", ["checkout", "tags/1.31.0"]); + if (checkout.exitCode !== 0) { + throw new Error(`Failed to checkout: ${checkout.stderr}`); + } +}); + +const ensureClean = register("vscode:clean", async (runner) => { + runner.cwd = vscodePath; + + const status = await runner.execute("git", ["status", "--porcelain"]); + if (status.stdout.trim() !== "") { + const clean = await runner.execute("git", ["clean", "-f", "-d", "-X"]); + if (clean.exitCode !== 0) { + throw new Error(`Failed to clean git repository: ${clean.stderr}`); + } + const removeUnstaged = await runner.execute("git", ["checkout", "--", "."]); + if (removeUnstaged.exitCode !== 0) { + throw new Error(`Failed to remove unstaged files: ${removeUnstaged.stderr}`); + } + } +}); + +const ensurePatched = register("vscode:patch", async (runner) => { + if (!fs.existsSync(vscodePath)) { + throw new Error("vscode must be cloned to patch"); + } + await ensureClean(); + + 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}`); + } +}); + +run(); diff --git a/package.json b/package.json index 523c92f5..2e0397b8 100644 --- a/package.json +++ b/package.json @@ -6,21 +6,21 @@ "description": "Run VS Code remotely.", "scripts": { "build:rules": "cd ./rules && tsc -p .", - "vscode:clone": "mkdir -p ./lib && test -d ./lib/vscode || git clone https://github.com/Microsoft/vscode/ ./lib/vscode", - "vscode:install": "cd ./lib/vscode && git checkout tags/1.30.1 && yarn", - "vscode": "npm-run-all vscode:*", "packages:install": "cd ./packages && yarn", - "postinstall": "npm-run-all --parallel vscode packages:install build:rules", - "start": "cd ./packages/server && yarn start", + "postinstall": "npm-run-all --parallel packages:install build:rules", + "start": "cd ./packages/server && yarn start", + "task": "ts-node -r tsconfig-paths/register build/tasks.ts", "test": "cd ./packages && yarn test" }, "devDependencies": { + "@types/fs-extra": "^5.0.4", "@types/node": "^10.12.18", "@types/trash": "^4.3.1", "crypto-browserify": "^3.12.0", "css-loader": "^2.1.0", "file-loader": "^3.0.1", "fork-ts-checker-webpack-plugin": "^0.5.2", + "fs-extra": "^7.0.1", "happypack": "^5.0.1", "html-webpack-plugin": "^3.2.0", "http-browserify": "^1.7.0", @@ -35,6 +35,7 @@ "style-loader": "^0.23.1", "ts-loader": "^5.3.3", "ts-node": "^7.0.1", + "tsconfig-paths": "^3.8.0", "tslint": "^5.12.1", "typescript": "^3.2.2", "typescript-tslint-plugin": "^0.2.1", diff --git a/packages/app/browser/package.json b/packages/app/browser/package.json new file mode 100644 index 00000000..e789ef3c --- /dev/null +++ b/packages/app/browser/package.json @@ -0,0 +1,10 @@ +{ + "name": "@coder/app", + "scripts": { + "start": "../../../node_modules/.bin/webpack-dev-server --config ./webpack.config.js", + "build": "../../../node_modules/.bin/webpack --config ./webpack.config.js" + }, + "dependencies": { + "material-components-web": "^0.44.0" + } +} diff --git a/packages/app/browser/src/app.html b/packages/app/browser/src/app.html new file mode 100644 index 00000000..16850c35 --- /dev/null +++ b/packages/app/browser/src/app.html @@ -0,0 +1,43 @@ + + + + + + Coder + + + +
+
+ <- Back

AWS Cloud

+

+ Enter server password +

+
+ + +
+
+
+
helper text
+
+
+
+ +
+ + + +
+
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/packages/app/browser/src/app.scss b/packages/app/browser/src/app.scss new file mode 100644 index 00000000..376befb9 --- /dev/null +++ b/packages/app/browser/src/app.scss @@ -0,0 +1,108 @@ +@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; +} diff --git a/packages/app/browser/src/app.ts b/packages/app/browser/src/app.ts new file mode 100644 index 00000000..b7019b97 --- /dev/null +++ b/packages/app/browser/src/app.ts @@ -0,0 +1,30 @@ +//@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) => window["t"] = 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 submit = document.getElementById("submit") as HTMLButtonElement; +if (!submit) { + throw new Error("No submit button found"); +} +submit.addEventListener("click", () => { + document.cookie = `password=${password.value}`; + location.reload(); +}); diff --git a/packages/app/browser/webpack.config.js b/packages/app/browser/webpack.config.js new file mode 100644 index 00000000..ac119e0e --- /dev/null +++ b/packages/app/browser/webpack.config.js @@ -0,0 +1,24 @@ +const path = require("path"); +const webpack = require("webpack"); +const merge = require("webpack-merge"); +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: prod ? "source-map" : "cheap-module-eval-source-map", + mode: prod ? "production" : "development", + output: { + path: path.join(__dirname, "out"), + }, + entry: [ + "webpack-hot-middleware/client?reload=true&quiet=true", + "./packages/app/browser/src/app.ts" + ], + plugins: [ + new HtmlWebpackPlugin({ + template: "packages/app/browser/src/app.html", + }), + new webpack.HotModuleReplacementPlugin(), + // new BundleAnalyzerPlugin(), + ] +}); diff --git a/packages/app/browser/yarn.lock b/packages/app/browser/yarn.lock new file mode 100644 index 00000000..f6f393c6 --- /dev/null +++ b/packages/app/browser/yarn.lock @@ -0,0 +1,518 @@ +# 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== + +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= diff --git a/packages/app/chrome/icon_128.png b/packages/app/chrome/icon_128.png new file mode 100644 index 00000000..11b0138c Binary files /dev/null and b/packages/app/chrome/icon_128.png differ diff --git a/packages/app/chrome/manifest.json b/packages/app/chrome/manifest.json new file mode 100644 index 00000000..e38c7166 --- /dev/null +++ b/packages/app/chrome/manifest.json @@ -0,0 +1,42 @@ +{ + "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": [ + "" + ] + } + } +} \ No newline at end of file diff --git a/packages/app/chrome/package.json b/packages/app/chrome/package.json new file mode 100644 index 00000000..3a727286 --- /dev/null +++ b/packages/app/chrome/package.json @@ -0,0 +1,9 @@ +{ + "name": "@coder/chrome-app", + "dependencies": { + "@types/chrome": "^0.0.79" + }, + "scripts": { + "build": "../../../node_modules/.bin/webpack --config ./webpack.config.js" + } +} diff --git a/packages/app/chrome/src/background.ts b/packages/app/chrome/src/background.ts new file mode 100644 index 00000000..84d87eb3 --- /dev/null +++ b/packages/app/chrome/src/background.ts @@ -0,0 +1,13 @@ +/// + +// tslint:disable-next-line:no-any +const chromeApp = (chrome).app; + +chromeApp.runtime.onLaunched.addListener(() => { + chromeApp.window.create("src/index.html", { + outerBounds: { + width: 400, + height: 500, + }, + }); +}); diff --git a/packages/app/chrome/src/chome.ts b/packages/app/chrome/src/chome.ts new file mode 100644 index 00000000..150ea898 --- /dev/null +++ b/packages/app/chrome/src/chome.ts @@ -0,0 +1,92 @@ +//@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 { + 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 + } = (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(); + + socketApi.tcpServer.onAccept.addListener((info) => { + if (info.socketId !== serverSocketId) { + return; + } + + const dataEmitter = new Emitter(); + + 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 => { + return new Promise((res): void => { + socketApi.tcp.send(info.clientSocketId, data, () => { + res(); + }); + }); + }, + close: (): Promise => { + return new Promise((res): void => { + socketApi.tcp.close(info.clientSocketId, () => { + res(); + }); + }); + }, + get onData(): Event { + return dataEmitter.event; + }, + }); + }); + + resolve({ + get onConnection(): Event { + return connectionEmitter.event; + }, + close: (): Promise => { + return new Promise((res): void => { + socketApi.tcpServer.disconnect(serverSocketId, () => { + res(); + }); + }); + }, + }); + }); + }); + }); + }, +}; diff --git a/packages/app/chrome/src/content.ts b/packages/app/chrome/src/content.ts new file mode 100644 index 00000000..64cc29ba --- /dev/null +++ b/packages/app/chrome/src/content.ts @@ -0,0 +1,33 @@ +import { create } from "@coder/app/common/src/app"; +import { tcpHost } from "./chome"; + +create({ + storage: { + get: (key: string): Promise => { + return new Promise((resolve, reject): void => { + try { + chrome.storage.sync.get(key, (items) => { + resolve(items[key]); + }); + } catch (ex) { + reject(ex); + } + }); + }, + set: (key: string, value: T): Promise => { + return new Promise((resolve, reject): void => { + try { + chrome.storage.sync.set({ + [key]: value, + }, () => { + resolve(); + }); + } catch (ex) { + reject(ex); + } + }); + }, + }, + tcp: tcpHost, + node: document.getElementById("main") as HTMLDivElement, +}); diff --git a/packages/app/chrome/src/index.html b/packages/app/chrome/src/index.html new file mode 100644 index 00000000..239fedd5 --- /dev/null +++ b/packages/app/chrome/src/index.html @@ -0,0 +1,15 @@ + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/packages/app/chrome/webpack.config.js b/packages/app/chrome/webpack.config.js new file mode 100644 index 00000000..6bfae8d0 --- /dev/null +++ b/packages/app/chrome/webpack.config.js @@ -0,0 +1,37 @@ +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: [ + ] + }), +]; diff --git a/packages/app/chrome/yarn.lock b/packages/app/chrome/yarn.lock new file mode 100644 index 00000000..61eebde6 --- /dev/null +++ b/packages/app/chrome/yarn.lock @@ -0,0 +1,22 @@ +# 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= diff --git a/packages/app/common/package.json b/packages/app/common/package.json new file mode 100644 index 00000000..1864e02b --- /dev/null +++ b/packages/app/common/package.json @@ -0,0 +1,13 @@ +{ + "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" + } +} diff --git a/packages/app/common/src/app.scss b/packages/app/common/src/app.scss new file mode 100644 index 00000000..99195139 --- /dev/null +++ b/packages/app/common/src/app.scss @@ -0,0 +1,279 @@ +@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; +} \ No newline at end of file diff --git a/packages/app/common/src/app.tsx b/packages/app/common/src/app.tsx new file mode 100644 index 00000000..65d457f8 --- /dev/null +++ b/packages/app/common/src/app.tsx @@ -0,0 +1,33 @@ +//@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 => { + let servers = await app.storage.get("servers"); + if (!servers) { + servers = []; + } + + render(
, app.node); +}; diff --git a/packages/app/common/src/connection.ts b/packages/app/common/src/connection.ts new file mode 100644 index 00000000..f4c1e022 --- /dev/null +++ b/packages/app/common/src/connection.ts @@ -0,0 +1,17 @@ +import { Event } from "@coder/events"; +import { TunnelCloseEvent } from "@coder/tunnel/src/client"; + +export interface TcpHost { + listen(host: string, port: number): Promise; +} + +export interface TcpServer { + readonly onConnection: Event; + close(): Promise; +} + +export interface TcpConnection { + readonly onData: Event; + send(data: ArrayBuffer): Promise; + close(): Promise; +} diff --git a/packages/app/common/src/containers.tsx b/packages/app/common/src/containers.tsx new file mode 100644 index 00000000..a3f73884 --- /dev/null +++ b/packages/app/common/src/containers.tsx @@ -0,0 +1,573 @@ +//@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 { + 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 ( +
+
+
+ +
+
+
+ {((): JSX.Element => { + switch (this.state.view) { + case "servers": + return ( + { + if (this.webview) { + this.webview.setAttribute("src", server.hostname); + } + }} + onAddServer={() => this.setState({ view: "add-server" })} + loading={this.state.loading} + /> + ); + case "add-server": + return ( +
Add server
+ ); + } + })()} +
+ this.webview = wv}> +
+ ); + } +} + +export class AddServer extends React.Component { + public render(): JSX.Element { + return ( +
+

Add Server

+

Something about what you can do once you add your own server. A link to setup guides for this would be great as well.

+ +

+
+ ); + } +} + +export class Servers extends React.Component<{ + readonly user?: AuthedUser; + readonly servers: ReadonlyArray; + 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 ( +
+
+

Servers

+ + +
+ 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"> + + + + + + + + + + +
+
+
+
+
+ Status +
+
+ Server Name +
+
+ Hostname +
+
+ Details +
+
+ {/* Used for continue/launch buttons */} +
+
+ {/* Used for logout and delete buttons */} +
+ +
{ + if (d) new MDCLinearProgress(d)}}> +
+
+
+ +
+
+ +
+
+ + {this.props.servers.map((server, i) => { + return ( + this.props.onSelect(server)} /> + ); + })} +
+
+ ); + } + + 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 { + // 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: "v1.31.0", + }); + } else { + this.setState({ + status: "Offline", + }); + } + }); + xhr.send(); + } + + public render(): JSX.Element { + return ( + <> +
+ {((): JSX.Element => { + switch (this.state.status) { + case "Offline": + return ( + + + + + + + + ); + case "Online": + return ( + + + + + + + + + ); + case "Checking": + return ( + + + + + + + ); + default: + throw new Error("unsupported status"); + } + })()} + + {this.state.status} + +
+
+ {this.props.server.host === "coder" ? ( + + ) : this.props.server.name} +
+
+ {this.props.server.hostname} +
+
+ {this.props.server.host === "coder" && this.props.user ? `Logged in as ${this.props.user.username}` : this.state.version} +
+
+ +
+
+ +
+ {this.props.server.host === "coder" ? ( + + + + + + + + + ) : ( + + + + + + + + + + )} +
+
+
+ + ); + } + + 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 ( +
this.wrapper = i}> + +
+
+
+ +
+
+
+
+ ); + } +} + +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 ( + + ); + } +} + +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 ( + + + + + + + + + + + + + + + + + + ); + } +} + +// const $ = (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 => { +// //
+// // +// // +// //
+// //
+// //
+// // +// //
+// //
+// //
+ +// 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")); +// }; diff --git a/packages/app/common/src/fonts/AktivGroteskBold.eot b/packages/app/common/src/fonts/AktivGroteskBold.eot new file mode 100644 index 00000000..4af6ee98 Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskBold.eot differ diff --git a/packages/app/common/src/fonts/AktivGroteskBold.ttf b/packages/app/common/src/fonts/AktivGroteskBold.ttf new file mode 100644 index 00000000..ef0b1dd2 Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskBold.ttf differ diff --git a/packages/app/common/src/fonts/AktivGroteskBold.woff b/packages/app/common/src/fonts/AktivGroteskBold.woff new file mode 100644 index 00000000..143aa7ce Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskBold.woff differ diff --git a/packages/app/common/src/fonts/AktivGroteskBold.woff2 b/packages/app/common/src/fonts/AktivGroteskBold.woff2 new file mode 100644 index 00000000..2ad9acc8 Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskBold.woff2 differ diff --git a/packages/app/common/src/fonts/AktivGroteskMedium.eot b/packages/app/common/src/fonts/AktivGroteskMedium.eot new file mode 100644 index 00000000..c1756e13 Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskMedium.eot differ diff --git a/packages/app/common/src/fonts/AktivGroteskMedium.ttf b/packages/app/common/src/fonts/AktivGroteskMedium.ttf new file mode 100644 index 00000000..3475b42d Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskMedium.ttf differ diff --git a/packages/app/common/src/fonts/AktivGroteskMedium.woff b/packages/app/common/src/fonts/AktivGroteskMedium.woff new file mode 100644 index 00000000..1fb3471f Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskMedium.woff differ diff --git a/packages/app/common/src/fonts/AktivGroteskMedium.woff2 b/packages/app/common/src/fonts/AktivGroteskMedium.woff2 new file mode 100644 index 00000000..7896573d Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskMedium.woff2 differ diff --git a/packages/app/common/src/fonts/AktivGroteskRegular.eot b/packages/app/common/src/fonts/AktivGroteskRegular.eot new file mode 100644 index 00000000..cd34e88c Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskRegular.eot differ diff --git a/packages/app/common/src/fonts/AktivGroteskRegular.ttf b/packages/app/common/src/fonts/AktivGroteskRegular.ttf new file mode 100644 index 00000000..a30d2ec3 Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskRegular.ttf differ diff --git a/packages/app/common/src/fonts/AktivGroteskRegular.woff b/packages/app/common/src/fonts/AktivGroteskRegular.woff new file mode 100644 index 00000000..3149aae5 Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskRegular.woff differ diff --git a/packages/app/common/src/fonts/AktivGroteskRegular.woff2 b/packages/app/common/src/fonts/AktivGroteskRegular.woff2 new file mode 100644 index 00000000..351e9ad1 Binary files /dev/null and b/packages/app/common/src/fonts/AktivGroteskRegular.woff2 differ diff --git a/packages/app/common/src/storage.ts b/packages/app/common/src/storage.ts new file mode 100644 index 00000000..b5f8221b --- /dev/null +++ b/packages/app/common/src/storage.ts @@ -0,0 +1,5 @@ + +export interface StorageProvider { + set(key: string, value: T): Promise; + get(key: string): Promise; +} diff --git a/packages/app/common/src/tooltip.scss b/packages/app/common/src/tooltip.scss new file mode 100644 index 00000000..6a4a93c9 --- /dev/null +++ b/packages/app/common/src/tooltip.scss @@ -0,0 +1,24 @@ +.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); +} diff --git a/packages/app/common/yarn.lock b/packages/app/common/yarn.lock new file mode 100644 index 00000000..fed23b62 --- /dev/null +++ b/packages/app/common/yarn.lock @@ -0,0 +1,601 @@ +# 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= diff --git a/packages/dns/.gcloudignore b/packages/dns/.gcloudignore new file mode 100644 index 00000000..b1a2026b --- /dev/null +++ b/packages/dns/.gcloudignore @@ -0,0 +1,18 @@ +# 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/ \ No newline at end of file diff --git a/packages/dns/Dockerfile b/packages/dns/Dockerfile new file mode 100644 index 00000000..376d334a --- /dev/null +++ b/packages/dns/Dockerfile @@ -0,0 +1,8 @@ +FROM node + +COPY out/main.js /main.js +COPY package.json /package.json +RUN yarn +ENV NODE_ENV production + +CMD ["node", "/main.js"] \ No newline at end of file diff --git a/packages/dns/app.yaml b/packages/dns/app.yaml new file mode 100644 index 00000000..6b329a92 --- /dev/null +++ b/packages/dns/app.yaml @@ -0,0 +1,5 @@ +runtime: nodejs10 +service: cdrdns +network: + forwarded_ports: + - 53/udp \ No newline at end of file diff --git a/packages/dns/package.json b/packages/dns/package.json new file mode 100644 index 00000000..a3817a77 --- /dev/null +++ b/packages/dns/package.json @@ -0,0 +1,14 @@ +{ + "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" + } +} diff --git a/packages/dns/src/dns.ts b/packages/dns/src/dns.ts new file mode 100644 index 00000000..06d3da77 --- /dev/null +++ b/packages/dns/src/dns.ts @@ -0,0 +1,109 @@ +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 +(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)); +}); diff --git a/packages/dns/src/words.ts b/packages/dns/src/words.ts new file mode 100644 index 00000000..a29a4933 --- /dev/null +++ b/packages/dns/src/words.ts @@ -0,0 +1,260 @@ +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); diff --git a/packages/dns/webpack.config.js b/packages/dns/webpack.config.js new file mode 100644 index 00000000..f0ecf2d2 --- /dev/null +++ b/packages/dns/webpack.config.js @@ -0,0 +1,18 @@ +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", + externals: { + "node-named": "commonjs node-named", + }, + output: { + path: path.join(__dirname, "out"), + filename: "main.js", + }, + entry: [ + "./packages/dns/src/dns.ts" + ], +}); diff --git a/packages/dns/yarn.lock b/packages/dns/yarn.lock new file mode 100644 index 00000000..c4abe2a7 --- /dev/null +++ b/packages/dns/yarn.lock @@ -0,0 +1,88 @@ +# 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= diff --git a/packages/ide/src/client.ts b/packages/ide/src/client.ts index 1c9b26f5..d67f7a8f 100644 --- a/packages/ide/src/client.ts +++ b/packages/ide/src/client.ts @@ -32,6 +32,13 @@ export abstract class IdeClient { logger.info("Loading IDE"); this.loadTime = time(2500); + let appWindow: Window | undefined; + window.addEventListener("message", (event) => { + if (event.data === "app") { + appWindow = event.source as Window; + } + }); + this.sharedProcessData = new Promise((resolve): void => { client.onSharedProcessActive(resolve); }); @@ -48,6 +55,9 @@ export abstract class IdeClient { 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)); diff --git a/packages/ide/src/fill/client.ts b/packages/ide/src/fill/client.ts index c076bae3..f9094383 100644 --- a/packages/ide/src/fill/client.ts +++ b/packages/ide/src/fill/client.ts @@ -92,7 +92,7 @@ class WebsocketConnection implements ReadWriteConnection { private async openSocket(): Promise { this.dispose(); const socket = new WebSocket( - `${location.protocol === "https" ? "wss" : "ws"}://${location.host}`, + `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}`, ); socket.binaryType = "arraybuffer"; this.activeSocket = socket; diff --git a/packages/ide/src/fill/electron.ts b/packages/ide/src/fill/electron.ts index a111f053..6cac1e5b 100644 --- a/packages/ide/src/fill/electron.ts +++ b/packages/ide/src/fill/electron.ts @@ -19,15 +19,59 @@ if (typeof document === "undefined") { (global as any).document = {} as any; } -const oldCreateElement: ( - tagName: K, options?: ElementCreationOptions, -) => HTMLElementTagNameMap[K] = document.createElement; - +const oldCreateElement = document.createElement; const newCreateElement = (tagName: K): HTMLElementTagNameMap[K] => { const createElement = (tagName: K): HTMLElementTagNameMap[K] => { - return oldCreateElement.call(document, tagName); + // tslint:disable-next-line:no-any + return oldCreateElement.call(document, tagName as any); }; + if (tagName === "style") { + const style = createElement("style"); + // 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); + }; + 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 => { + value = value.replace(/file:\/\//g, "/resource"); + 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 => { + rule = rule.replace(/file:\/\//g, "/resource"); + oldInsertRule.call(sheet, rule, index); + }; + overridden = true; + } + + return sheet; + }, + }); + + return style; + } + if (tagName === "webview") { const view = createElement("iframe") as HTMLIFrameElement; view.style.border = "0px"; @@ -56,8 +100,19 @@ const newCreateElement = (tagName: K): HT 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); } + }; }, }); @@ -65,8 +120,8 @@ const newCreateElement = (tagName: K): HT (view as any).send = (channel: string, ...args: any[]): void => { // tslint:disable-line no-any if (args[0] && typeof args[0] === "object" && args[0].contents) { // TODO - // args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m) => `"${getFetchUrl(m)}"`); - // args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m) => `"${getFetchUrl(m)}"`); + args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m1) => `"/resource${m1}"`); + args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m, m1) => `"/resource${m1}"`); } if (view.contentWindow) { view.contentWindow.postMessage({ diff --git a/packages/ide/src/fill/fs.ts b/packages/ide/src/fill/fs.ts index f5ada92b..55835223 100644 --- a/packages/ide/src/fill/fs.ts +++ b/packages/ide/src/fill/fs.ts @@ -118,6 +118,7 @@ class FS { const str = fs.createWriteStream(path, options); ae.on("write", (d: string) => str.write(_Buffer.from(d, "utf8"))); ae.on("close", () => str.close()); + ae.on("destroy", () => str.destroy()); str.on("close", () => ae.emit("close")); str.on("open", (fd) => ae.emit("open", fd)); str.on("error", (err) => ae.emit(err)); @@ -157,6 +158,10 @@ class FS { ae.emit("close"); } + public destroy(): void { + ae.emit("destroy"); + } + }) as fs.WriteStream; } diff --git a/packages/ide/src/fill/os.ts b/packages/ide/src/fill/os.ts index 57ee6276..3d247b75 100644 --- a/packages/ide/src/fill/os.ts +++ b/packages/ide/src/fill/os.ts @@ -32,6 +32,10 @@ class OS { this._tmpdir = data.tmpDirectory; } + public release(): string { + return "Unknown"; + } + public platform(): NodeJS.Platform { if (navigator.appVersion.indexOf("Win") != -1) { return "win32"; diff --git a/packages/runner/package.json b/packages/runner/package.json new file mode 100644 index 00000000..3ff580b4 --- /dev/null +++ b/packages/runner/package.json @@ -0,0 +1,4 @@ +{ + "name": "@coder/runner", + "main": "src/index.ts" +} \ No newline at end of file diff --git a/packages/runner/src/index.ts b/packages/runner/src/index.ts new file mode 100644 index 00000000..bd2b893c --- /dev/null +++ b/packages/runner/src/index.ts @@ -0,0 +1 @@ +export * from "./runner"; diff --git a/packages/runner/src/runner.ts b/packages/runner/src/runner.ts new file mode 100644 index 00000000..0f80acdb --- /dev/null +++ b/packages/runner/src/runner.ts @@ -0,0 +1,114 @@ +import * as cp from "child_process"; +import { logger, Logger, field, time } from "@coder/logger"; + +export interface CommandResult { + readonly exitCode: number; + readonly stdout: string; + readonly stderr: string; +} + +const execute = (command: string, args: string[] = [], options: cp.SpawnOptions, logger: Logger): Promise => { + let resolve: (result: CommandResult) => void; + const prom = new Promise(res => resolve = res); + + const stdout: string[] = []; + const stderr: string[] = []; + const complete = (exitCode: number): void => { + resolve({ + stderr: stderr.join(""), + stdout: stdout.join(""), + exitCode, + }); + }; + logger.info(`Executing '${command} ${JSON.stringify(args)}'`, field("options", options)); + const proc = cp.spawn(command, args.length > 0 ? args : [], options); + proc.on("close", (code) => { + complete(code); + }); + proc.on("exit", (code) => { + complete(code!); + }); + proc.stdout.on("data", (d) => { + stdout.push(d.toString()); + logger.debug("stdio", field("stdout", d.toString())); + }); + proc.stderr.on("data", (d) => { + stderr.push(d.toString()); + logger.debug("stdio", field("stderr", d.toString())); + }); + + return prom; +}; + +export type TaskFunction = (runner: Runner) => void | Promise; + +export interface Runner { + + cwd: string; + execute(command: string, args?: string[], env?: object): Promise; +} + +export interface Task { + readonly name: string; + readonly func: TaskFunction; +} + +const tasks = new Map(); +const activated = new Map>(); + +export const register = (name: string, func: TaskFunction): () => void | Promise => { + if (tasks.has(name)) { + throw new Error(`Task "${name}" already registered`); + } + + tasks.set(name, { + name, + func, + }); + + return (): void | Promise => { + return run(name); + }; +}; + +export const run = (name: string = process.argv[2]): void | Promise => { + const task = tasks.get(name); + if (!task) { + logger.error("Task not found.", field("name", name), field("available", Array.from(tasks.keys()))); + + return process.exit(1); + } + if (activated.has(name)) { + return activated.get(name); + } + let cwd: string = process.cwd(); + const log = logger.named(name); + const timer = time(Number.MAX_SAFE_INTEGER); + log.info("Starting..."); + const prom = task.func({ + set cwd(path: string) { + cwd = path; + }, + execute(command: string, args: string[] = [], env?: object): Promise { + return execute(command, args, { + cwd, + env: env as NodeJS.ProcessEnv, + }, log); + }, + }); + + if (prom) { + activated.set(name, prom); + prom.then(() => { + log.info("Completed!", field("time", timer)); + }).catch((ex) => { + activated.delete(name); + log.error(`Failed: ${ex.message}`); + log.error(`Stack: ${ex.stack}`); + + return process.exit(1); + }); + } + + return prom; +}; diff --git a/packages/runner/yarn.lock b/packages/runner/yarn.lock new file mode 100644 index 00000000..fb57ccd1 --- /dev/null +++ b/packages/runner/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/packages/server/README.md b/packages/server/README.md new file mode 100644 index 00000000..708280d6 --- /dev/null +++ b/packages/server/README.md @@ -0,0 +1,16 @@ +# server + +## Endpoints + +### `/tunnel/` + +Tunnels a TCP connection over WebSockets. Implemented for proxying connections from a remote machine locally. + +### `/ports` + +Watches for open ports. Implemented for tunneling ports on the remote server. + +### `/resource/` + +Reads files on GET. +Writes files on POST. \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index 3064e193..ddc62f45 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,9 +18,12 @@ "@oclif/plugin-help": "^2.1.4", "express": "^4.16.4", "express-static-gzip": "^1.1.3", + "httpolyglot": "^0.1.2", "mime-types": "^2.1.21", "nexe": "^2.0.0-rc.34", - "segfault-handler": "^1.0.1", + "node-netstat": "^1.6.0", + "pem": "^1.14.1", + "promise.prototype.finally": "^3.1.0", "spdlog": "^0.7.2", "ws": "^6.1.2", "xhr2": "^0.1.4" @@ -29,6 +32,7 @@ "@types/express": "^4.16.0", "@types/fs-extra": "^5.0.4", "@types/mime-types": "^2.1.0", + "@types/pem": "^1.9.4", "@types/ws": "^6.0.1", "fs-extra": "^7.0.1", "string-replace-webpack-plugin": "^0.1.3", diff --git a/packages/server/scripts/nexe.js b/packages/server/scripts/nexe.js index dec568d8..042955d8 100644 --- a/packages/server/scripts/nexe.js +++ b/packages/server/scripts/nexe.js @@ -3,57 +3,12 @@ const fse = require("fs-extra"); const os = require("os"); const path = require("path"); const nexe = require("nexe"); -const zlib = require("zlib"); - -const nexeRoot = path.join(os.homedir(), ".nexe"); -if (!fs.existsSync(nexeRoot)) { - throw new Error("run nexe manually on a binary to initialize it"); -} -const listed = fs.readdirSync(nexeRoot); -listed.forEach((list) => { - if (list.startsWith("linux")) { - const stat = fs.statSync(path.join(nexeRoot, list)); - if (stat.isFile()) { - if (stat.size > 20000000) { - throw new Error("must use upx to shrink node binary in ~/.nexe/" + list); - } - } - } -}); - -const tmpDir = path.join(__dirname, "../build"); -if (fs.existsSync(tmpDir)) { - console.log("Removing old build dir..."); - fse.removeSync(tmpDir); -} - -console.log("Copying build files..."); -fse.copySync(path.join(__dirname, "../resources"), tmpDir, { - recursive: true, -}); -const modDir = path.join(tmpDir, "modules"); -fs.mkdirSync(modDir); -fse.copySync(path.join(__dirname, "../../protocol/node_modules/node-pty/build/Release/pty.node"), path.join(modDir, "pty.node")); - -const zipper = (p) => { - const stat = fs.statSync(p); - if (!stat.isDirectory()) { - fs.writeFileSync(p + ".gz", zlib.gzipSync(fs.readFileSync(p))); - fse.removeSync(p); - return; - } - const files = fs.readdirSync(p); - files.forEach((f) => zipper(path.join(p, f))); -}; - -zipper(path.join(tmpDir, "web")); -zipper(path.join(tmpDir, "bootstrap-fork.js")); nexe.compile({ debugBundle: true, input: path.join(__dirname, "../out/cli.js"), - output: 'cli', - targets: ["linux"], + output: `cli-${process.env.TRAVIS_OS_NAME || os.platform()}`, + targets: [os.platform()], native: { spdlog: { additionalFiles: [ diff --git a/packages/server/src/cli.ts b/packages/server/src/cli.ts index 5df1da7c..dd3740e3 100644 --- a/packages/server/src/cli.ts +++ b/packages/server/src/cli.ts @@ -6,7 +6,7 @@ import * as os from "os"; import * as path from "path"; import * as WebSocket from "ws"; import { createApp } from "./server"; -import { requireModule } from "./vscode/bootstrapFork"; +import { requireModule, requireFork } from "./vscode/bootstrapFork"; import { SharedProcess, SharedProcessState } from "./vscode/sharedProcess"; import { setup as setupNativeModules } from "./modules"; import { fillFs } from "./fill"; @@ -26,6 +26,8 @@ export class Entry extends Command { // Dev flags "bootstrap-fork": flags.string({ hidden: true }), + "fork": flags.string({ hidden: true }), + env: flags.string({ hidden: true }), args: flags.string({ hidden: true }), }; @@ -76,6 +78,12 @@ export class Entry extends Command { return requireModule(modulePath, builtInExtensionsDir); } + if (flags["fork"]) { + const modulePath = flags["fork"]; + + return requireFork(modulePath, JSON.parse(flags.args!), builtInExtensionsDir); + } + const dataDir = flags["data-dir"] || path.join(os.homedir(), ".vscode-remote"); const workingDir = args["workdir"]; @@ -92,6 +100,38 @@ export class Entry extends Command { const logDir = path.join(dataDir, "logs", new Date().toISOString().replace(/[-:.TZ]/g, "")); process.env.VSCODE_LOGS = logDir; + const certPath = flags.cert; + const certKeyPath = flags["cert-key"]; + + if (certPath && !certKeyPath) { + logger.error("'--cert-key' flag is required when specifying a certificate!"); + process.exit(1); + } + + if (!certPath && certKeyPath) { + logger.error("'--cert' flag is required when specifying certificate key!"); + process.exit(1); + } + + let certData: Buffer | undefined; + let certKeyData: Buffer | undefined; + + if (typeof certPath !== "undefined" && typeof certKeyPath !== "undefined") { + try { + certData = fs.readFileSync(certPath); + } catch (ex) { + logger.error(`Failed to read certificate: ${ex.message}`); + process.exit(1); + } + + try { + certKeyData = fs.readFileSync(certKeyPath); + } catch (ex) { + logger.error(`Failed to read certificate key: ${ex.message}`); + process.exit(1); + } + } + logger.info("\u001B[1mvscode-remote v1.0.0"); // TODO: fill in appropriate doc url logger.info("Additional documentation: https://coder.com/docs"); @@ -111,7 +151,9 @@ export class Entry extends Command { } }); - const app = createApp((app) => { + const password = "023450wf0951"; + const hasCustomHttps = certData && certKeyData; + const app = await createApp((app) => { app.use((req, res, next) => { res.on("finish", () => { logger.trace(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.url}`, field("host", req.hostname), field("ip", req.ip)); @@ -132,10 +174,13 @@ export class Entry extends Command { app.use(require("webpack-hot-middleware")(compiler)); } }, { - builtInExtensionsDirectory: builtInExtensionsDir, - dataDirectory: dataDir, - workingDirectory: workingDir, - }); + builtInExtensionsDirectory: builtInExtensionsDir, + dataDirectory: dataDir, + workingDirectory: workingDir, + }, password, hasCustomHttps ? { + key: certKeyData, + cert: certData, + } : undefined); logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port)); app.server.listen(flags.port, flags.host); @@ -161,7 +206,7 @@ export class Entry extends Command { } logger.info(" "); - logger.info("Password:\u001B[1m 023450wf09"); + logger.info(`Password:\u001B[1m ${password}`); logger.info(" "); logger.info("Started (click the link below to open):"); logger.info(`http://localhost:${flags.port}/`); diff --git a/packages/server/src/fill.ts b/packages/server/src/fill.ts index 94c373cd..d353d8a7 100644 --- a/packages/server/src/fill.ts +++ b/packages/server/src/fill.ts @@ -49,7 +49,11 @@ export const fillFs = (): void => { } }; if (customPromisify) { - (fs[propertyName])[util.promisify.custom] = customPromisify; + (fs[propertyName])[util.promisify.custom] = (...args: any[]) => { + return customPromisify(...args).catch((ex) => { + throw ex; + }); + }; } }; @@ -113,13 +117,6 @@ export const fillFs = (): void => { const fileDesc = fds.get(fd)!; - /** - * `readFile` is filled within nexe, but `read` is not - * https://github.com/nexe/nexe/blob/master/src/fs/patch.ts#L199 - * We can simulate a real _read_ by reading the entire file. - * Efficiency can be improved here by storing the entire file in memory - * until it has been closed. - */ return fs.readFile(fileDesc.path, (err, rb) => { if (err) { return callOld(); diff --git a/packages/server/src/modules.ts b/packages/server/src/modules.ts index d8af91d5..4f298754 100644 --- a/packages/server/src/modules.ts +++ b/packages/server/src/modules.ts @@ -35,8 +35,10 @@ export const setup = (dataDirectory: string): void => { */ unpackModule("pty"); const nodePtyUtils = require("../../protocol/node_modules/node-pty/lib/utils") as typeof import("../../protocol/node_modules/node-pty/src/utils"); - nodePtyUtils.loadNative = (modName: string) => { + // tslint:disable-next-line:no-any + nodePtyUtils.loadNative = (modName: string): any => { return __non_webpack_require__(path.join(dataDirectory, "modules", modName + ".node")); }; + // tslint:disable-next-line:no-unused-expression require("../../protocol/node_modules/node-pty/lib/index") as typeof import("../../protocol/node_modules/node-pty/src/index"); }; diff --git a/packages/server/src/portScanner.ts b/packages/server/src/portScanner.ts new file mode 100644 index 00000000..1bb580a5 --- /dev/null +++ b/packages/server/src/portScanner.ts @@ -0,0 +1,105 @@ +//@ts-ignore +import * as netstat from "node-netstat"; +import { Event, Emitter } from "@coder/events"; + +export interface PortScanner { + readonly ports: ReadonlyArray; + + readonly onAdded: Event>; + readonly onRemoved: Event>; + + dispose(): void; +} + +/** + * Creates a disposable port scanner. + * Will scan local ports and emit events when ports are added or removed. + * Currently only scans TCP ports. + */ +export const createPortScanner = (scanInterval: number = 250): PortScanner => { + const ports = new Map(); + + const addEmitter = new Emitter(); + const removeEmitter = new Emitter(); + + const scan = (onCompleted: (err?: Error) => void): void => { + const scanTime = Date.now(); + const added: number[] = []; + netstat({ + done: (err: Error): void => { + const removed: number[] = []; + ports.forEach((value, key) => { + if (value !== scanTime) { + // Remove port + removed.push(key); + ports.delete(key); + } + }); + if (removed.length > 0) { + removeEmitter.emit(removed); + } + + if (added.length > 0) { + addEmitter.emit(added); + } + + onCompleted(err); + }, + filter: { + state: "LISTEN", + }, + }, (data: { + readonly protocol: string; + readonly local: { + readonly port: number; + readonly address: string; + }; + }) => { + // https://en.wikipedia.org/wiki/Registered_port + if (data.local.port <= 1023 || data.local.port >= 49151) { + return; + } + // Only forward TCP ports + if (!data.protocol.startsWith("tcp")) { + return; + } + + if (!ports.has(data.local.port)) { + added.push(data.local.port); + } + ports.set(data.local.port, scanTime); + }); + }; + + let lastTimeout: NodeJS.Timer | undefined; + let disposed: boolean = false; + + const doInterval = (): void => { + scan(() => { + if (disposed) { + return; + } + lastTimeout = setTimeout(doInterval, scanInterval); + }); + }; + + doInterval(); + + return { + get ports(): number[] { + return Array.from(ports.keys()); + }, + get onAdded(): Event { + return addEmitter.event; + }, + get onRemoved(): Event { + return removeEmitter.event; + }, + dispose(): void { + if (typeof lastTimeout !== "undefined") { + clearTimeout(lastTimeout); + } + disposed = true; + }, + }; +}; diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index dfca651b..a3bfb99a 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1,4 +1,4 @@ -import { logger } from "@coder/logger"; +import { logger, field } from "@coder/logger"; import { ReadWriteConnection } from "@coder/protocol"; import { Server, ServerOptions } from "@coder/protocol/src/node/server"; import * as express from "express"; @@ -6,30 +6,116 @@ import * as express from "express"; import * as expressStaticGzip from "express-static-gzip"; import * as fs from "fs"; import * as http from "http"; +//@ts-ignore +import * as httpolyglot from "httpolyglot"; +import * as https from "https"; import * as mime from "mime-types"; +import * as net from "net"; import * as path from "path"; +import * as pem from "pem"; import * as util from "util"; import * as ws from "ws"; -import { isCli, buildDir } from "./constants"; +import { TunnelCloseCode } from "@coder/tunnel/src/common"; +import { handle as handleTunnel } from "@coder/tunnel/src/server"; +import { createPortScanner } from "./portScanner"; +import { buildDir, isCli } from "./constants"; -export const createApp = (registerMiddleware?: (app: express.Application) => void, options?: ServerOptions): { +export const createApp = async (registerMiddleware?: (app: express.Application) => void, options?: ServerOptions, password?: string, httpsOptions?: https.ServerOptions): Promise<{ readonly express: express.Application; readonly server: http.Server; readonly wss: ws.Server; -} => { +}> => { + const parseCookies = (req: http.IncomingMessage): { [key: string]: string } => { + const cookies: { [key: string]: string } = {}; + const rc = req.headers.cookie; + if (rc) { + rc.split(";").forEach((cook) => { + const parts = cook.split("="); + cookies[parts.shift()!.trim()] = decodeURI(parts.join("=")); + }); + } + + return cookies; + }; + + const isAuthed = (req: http.IncomingMessage): boolean => { + try { + if (!password || !isCli) { + return true; + } + + // Try/catch placed here just in case + const cookies = parseCookies(req); + if (cookies.password && cookies.password === password) { + return true; + } + } catch (ex) { + logger.error("Failed to parse cookies", field("error", ex)); + } + + return false; + }; + + const isEncrypted = (socket: net.Socket): boolean => { + // tslint:disable-next-line:no-any + return (socket as any).encrypted; + }; + const app = express(); if (registerMiddleware) { registerMiddleware(app); } - const server = http.createServer(app); + + const certs = await new Promise((res, rej): void => { + pem.createCertificate({ + selfSigned: true, + }, (err, result) => { + if (err) { + rej(err); + + return; + } + + res(result); + }); + }); + + const server = httpolyglot.createServer({ + key: certs.serviceKey, + cert: certs.certificate, + }, app) as http.Server; const wss = new ws.Server({ server }); wss.shouldHandle = (req): boolean => { - // Should handle auth here - return true; + return isAuthed(req); }; - wss.on("connection", (ws) => { + const portScanner = createPortScanner(); + wss.on("connection", (ws, req) => { + if (req.url && req.url.startsWith("/tunnel")) { + try { + const rawPort = req.url.split("/").pop(); + const port = Number.parseInt(rawPort!, 10); + + handleTunnel(ws, port); + } catch (ex) { + ws.close(TunnelCloseCode.Error, ex.toString()); + } + + return; + } + + if (req.url && req.url.startsWith("/ports")) { + const onAdded = portScanner.onAdded((added) => ws.send(JSON.stringify({ added }))); + const onRemoved = portScanner.onRemoved((removed) => ws.send(JSON.stringify({ removed }))); + ws.on("close", () => { + onAdded.dispose(); + onRemoved.dispose(); + }); + + return ws.send(JSON.stringify({ ports: portScanner.ports })); + } + const connection: ReadWriteConnection = { onMessage: (cb): void => { ws.addEventListener("message", (event) => cb(event.data)); @@ -52,11 +138,17 @@ export const createApp = (registerMiddleware?: (app: express.Application) => voi }); const baseDir = buildDir || path.join(__dirname, ".."); - if (isCli) { - app.use(expressStaticGzip(path.join(baseDir, "build/web"))); - } else { - app.use(express.static(path.join(baseDir, "resources/web"))); - } + const authStaticFunc = expressStaticGzip(path.join(baseDir, "build/web/auth")); + const unauthStaticFunc = expressStaticGzip(path.join(baseDir, "build/web/unauth")); + app.use((req, res, next) => { + if (isAuthed(req)) { + // We can serve the actual VSCode bin + authStaticFunc(req, res, next); + } else { + // Serve only the unauthed version + unauthStaticFunc(req, res, next); + } + }); app.get("/resource/:url(*)", async (req, res) => { try { const fullPath = `/${req.params.url}`; @@ -91,6 +183,28 @@ export const createApp = (registerMiddleware?: (app: express.Application) => voi res.end(); } }); + app.post("/resource/:url(*)", async (req, res) => { + try { + const fullPath = `/${req.params.url}`; + + const data: string[] = []; + req.setEncoding("utf8"); + req.on("data", (chunk) => { + data.push(chunk); + }); + req.on("end", () => { + const body = data.join(""); + fs.writeFileSync(fullPath, body); + logger.debug("Wrote resource", field("path", fullPath), field("content-length", body.length)); + res.status(200); + res.end(); + }); + } catch (ex) { + res.write(ex.toString()); + res.status(500); + res.end(); + } + }); return { express: app, diff --git a/packages/server/src/vscode/bootstrapFork.ts b/packages/server/src/vscode/bootstrapFork.ts index c41a5443..b6b6de88 100644 --- a/packages/server/src/vscode/bootstrapFork.ts +++ b/packages/server/src/vscode/bootstrapFork.ts @@ -5,37 +5,104 @@ import * as zlib from "zlib"; import * as vm from "vm"; import { isCli } from "../constants"; +let ipcMsgBuffer: Buffer[] | undefined = []; +let ipcMsgListener = process.send ? (d: Buffer): number => ipcMsgBuffer!.push(d) : undefined; +if (ipcMsgListener) { + process.on("message", ipcMsgListener); +} + +/** + * Requires a module from the filesystem. + * + * Will load from the CLI if file is included inside of the default extensions dir + */ +// tslint:disable-next-line:no-any +const requireFilesystemModule = (id: string, builtInExtensionsDir: string): any => { + const mod = require("module") as typeof import("module"); + const customMod = new mod.Module(id); + customMod.filename = id; + // tslint:disable-next-line:no-any + customMod.paths = [(mod)._nodeModulePaths(path.dirname(id)), path.join(__dirname, "../../../../lib/vscode/node_modules")]; + + if (id.startsWith(builtInExtensionsDir)) { + customMod.loaded = true; + const fileName = id.endsWith(".js") ? id : `${id}.js`; + const req = vm.runInThisContext(mod.wrap(fs.readFileSync(fileName).toString()), { + displayErrors: true, + filename: id + fileName, + }); + req(customMod.exports, customMod.require.bind(customMod), customMod, fileName, path.dirname(id)); + + return customMod.exports; + } + + return customMod.require(id); +}; + +/** + * Called from forking a module + */ +export const requireFork = (modulePath: string, args: string[], builtInExtensionsDir: string): void => { + const Module = require("module") as typeof import("module"); + const oldRequire = Module.prototype.require; + // tslint:disable-next-line:no-any + Module.prototype.require = (id: string): any => { + if (id === "typescript") { + return require("typescript"); + } + + return oldRequire(id); + }; + + if (!process.send) { + throw new Error("No IPC messaging initialized"); + } + + process.argv = ["", "", ...args]; + + requireFilesystemModule(modulePath, builtInExtensionsDir); + + if (ipcMsgBuffer && ipcMsgListener) { + process.removeListener("message", ipcMsgListener); + // tslint:disable-next-line:no-any + ipcMsgBuffer.forEach((i) => process.emit("message" as any, i as any)); + ipcMsgBuffer = undefined; + ipcMsgListener = undefined; + } +}; + export const requireModule = (modulePath: string, builtInExtensionsDir: string): void => { process.env.AMD_ENTRYPOINT = modulePath; const xml = require("xhr2"); + xml.XMLHttpRequest.prototype._restrictedHeaders["user-agent"] = false; // tslint:disable-next-line no-any this makes installing extensions work. (global as any).XMLHttpRequest = xml.XMLHttpRequest; const mod = require("module") as typeof import("module"); + const promiseFinally = require("promise.prototype.finally") as { shim: () => void }; + promiseFinally.shim(); /** * Used for loading extensions. Using __non_webpack_require__ didn't work * as it was not resolving to the FS. */ - (global as any).nativeNodeRequire = (id: string): any => {// tslint:disable-line no-any - const customMod = new mod.Module(id); - customMod.filename = id; - // tslint:disable-next-line no-any - customMod.paths = (mod as any)._nodeModulePaths(path.dirname(id)); - - if (id.startsWith(builtInExtensionsDir)) { - customMod.loaded = true; - const req = vm.runInThisContext(mod.wrap(fs.readFileSync(id + ".js").toString()), { - displayErrors: true, - filename: id + ".js", - }); - req(customMod.exports, customMod.require.bind(customMod), customMod, __filename, path.dirname(id)); - - return customMod.exports; - } - - return customMod.require(id); + // tslint:disable-next-line:no-any + (global as any).nativeNodeRequire = (id: string): any => { + return requireFilesystemModule(id, builtInExtensionsDir); }; + if (isCli) { + /** + * Needed for properly forking external modules within the CLI + */ + // tslint:disable-next-line:no-any + (cp).fork = (modulePath: string, args: ReadonlyArray = [], options?: cp.ForkOptions): cp.ChildProcess => { + return cp.spawn(process.execPath, ["--fork", modulePath, "--args", JSON.stringify(args)], { + ...options, + stdio: [null, null, null, "ipc"], + }); + }; + } + let content: Buffer | undefined; const readFile = (name: string): Buffer => { return fs.readFileSync(path.join(process.env.BUILD_DIR as string || path.join(__dirname, "../.."), "./build", name)); @@ -43,7 +110,7 @@ export const requireModule = (modulePath: string, builtInExtensionsDir: string): if (isCli) { content = zlib.gunzipSync(readFile("bootstrap-fork.js.gz")); } else { - content = readFile("../resources/bootstrap-fork.js"); + content = readFile("../../vscode/bin/bootstrap-fork.js"); } eval(content.toString()); }; @@ -71,7 +138,7 @@ export const forkModule = (modulePath: string, args: string[], options: cp.ForkO stdio: [null, null, null, "ipc"], }; if (isCli) { - proc = cp.execFile(process.execPath, forkArgs, forkOptions); + proc = cp.spawn(process.execPath, forkArgs, options); } else { proc = cp.spawn(process.execPath, ["--require", "ts-node/register", "--require", "tsconfig-paths/register", process.argv[1], ...forkArgs], forkOptions); } diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 4d8dd1c1..23add6b9 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -129,6 +129,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== +"@types/pem@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.4.tgz#9ef9302dc5f0352503e193003b208cddef4ffa45" + integrity sha512-cLRUgpedqF4lnQxDsjbRCgHRPHaJvnsHC+LEBTKnChddoPYJYQMq/LjSsEDwvRteeJV8MGt7Ea9jYCBVufrcNg== + dependencies: + "@types/node" "*" + "@types/range-parser@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" @@ -430,13 +437,6 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== -bindings@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.4.0.tgz#909efa49f2ebe07ecd3cb136778f665052040127" - integrity sha512-7znEVX22Djn+nYjxCWKDne0RRloa9XfYa84yk3s+HkE3LpDYZmhArYr9O9huBoHY3/oXispx5LorIX7Sl2CgSQ== - dependencies: - file-uri-to-path "1.0.0" - bindings@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" @@ -624,6 +624,11 @@ chardet@^0.4.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + chokidar@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -800,6 +805,11 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + css-loader@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.9.1.tgz#2e1aa00ce7e30ef2c6a7a4b300a080a7c979e0dc" @@ -915,6 +925,13 @@ deepmerge@^2.0.1: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== +define-properties@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -1019,6 +1036,32 @@ end-of-stream@^1.0.0: dependencies: once "^1.4.0" +es-abstract@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-promisify@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.0.1.tgz#6edaa45f3bd570ffe08febce66f7116be4b1cdb6" + integrity sha512-J3ZkwbEnnO+fGAKrjVpeUAnZshAdfZvbhQpqfIH9kSAspReRC4nJnu8ewm55b4y9ElyeuhCTzJD0XiH8Tsbhlw== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -1279,11 +1322,6 @@ file-type@^6.1.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -1429,6 +1467,11 @@ fsevents@^1.0.0: nan "^2.9.2" node-pre-gyp "^0.10.0" +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + fuse-box@^3.1.0: version "3.6.0" resolved "https://registry.yarnpkg.com/fuse-box/-/fuse-box-3.6.0.tgz#a4adf41a60855c7b0a0775b3095b6b0c2d35b011" @@ -1645,6 +1688,11 @@ has-symbol-support-x@^1.4.1: resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" @@ -1688,6 +1736,13 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + http-cache-semantics@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" @@ -1712,6 +1767,11 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +httpolyglot@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/httpolyglot/-/httpolyglot-0.1.2.tgz#e4d347fe8984a62f467d4060df527f1851f6997b" + integrity sha1-5NNH/omEpi9GfUBg31J/GFH2mXs= + iconv-lite@0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" @@ -1815,11 +1875,16 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5: +is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -1834,6 +1899,11 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -1956,6 +2026,13 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" + is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" @@ -1966,6 +2043,13 @@ is-stream@^1.0.0, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -1976,11 +2060,21 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -2192,6 +2286,15 @@ math-random@^1.0.1: resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -2344,7 +2447,7 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.0.9, nan@^2.8.0, nan@^2.9.2: +nan@^2.8.0, nan@^2.9.2: version "2.12.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== @@ -2406,6 +2509,13 @@ nexe@^2.0.0-rc.34: uglify-es "^3.3.9" uglify-js "3.0.28" +node-netstat@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/node-netstat/-/node-netstat-1.6.0.tgz#38c36b5f966b00ffaa2ed6f6321e6ad4487d8c89" + integrity sha512-KPDopkvPllhcILoHMWYUxvOO5c+VcPB38LxlOFPiZhZ/hJTMH/GXGCs6nvxu4d6unwsbEfgzJ4pPye3CFv9yTg== + dependencies: + is-wsl "^1.1.0" + node-pre-gyp@^0.10.0: version "0.10.3" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" @@ -2501,6 +2611,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -2576,7 +2691,7 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -2660,6 +2775,16 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +pem@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/pem/-/pem-1.14.1.tgz#8ff3c5884bfcba7bbdfea5b67a7fa24b4ca3bb86" + integrity sha512-WY3IzMoh+Gwp4xJTT2MqIOaVzNqU7jHqj7k0pOnLIkNSnOpjhy3PHr9mXGi+C5tRC2z1EX5lvzEbd9BtHumHLQ== + dependencies: + es6-promisify "^6.0.0" + md5 "^2.2.1" + os-tmpdir "^1.0.1" + which "^1.3.1" + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -2744,6 +2869,15 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== +promise.prototype.finally@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e" + integrity sha512-7p/K2f6dI+dM8yjRQEGrTQs5hTQixUAdOGpMEA3+pVxpX5oHKRSKAXyLw9Q9HUWDTdwtoo39dSHGQtN90HcEwQ== + dependencies: + define-properties "^1.1.2" + es-abstract "^1.9.0" + function-bind "^1.1.1" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -3022,14 +3156,6 @@ seek-bzip@^1.0.5: dependencies: commander "~2.8.1" -segfault-handler@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/segfault-handler/-/segfault-handler-1.0.1.tgz#9466d8f77d8826cfbdfa811124ece02983fd6ad2" - integrity sha512-3koBV3F0IPxTYacnoL7WoFlaMndXsXj62tiVbbW9Kzp3K+F9Y6GWW5XmKSv7j+7nf2M+qjNzc4W1iZoa8vocjw== - dependencies: - bindings "^1.2.1" - nan "^2.0.9" - semver@^5.3.0, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -3668,6 +3794,13 @@ watch@^1.0.1: exec-sh "^0.2.0" minimist "^1.2.0" +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" diff --git a/packages/tunnel/package.json b/packages/tunnel/package.json new file mode 100644 index 00000000..da3dc84f --- /dev/null +++ b/packages/tunnel/package.json @@ -0,0 +1,4 @@ +{ + "name": "@coder/tunnel", + "main": "src/tunnel.ts" +} \ No newline at end of file diff --git a/packages/tunnel/src/client.ts b/packages/tunnel/src/client.ts new file mode 100644 index 00000000..5941096a --- /dev/null +++ b/packages/tunnel/src/client.ts @@ -0,0 +1,48 @@ +import { Event, Emitter } from "@coder/events"; +import { TunnelCloseCode } from "./common"; + +export interface TunnelCloseEvent { + readonly code: TunnelCloseCode; + readonly reason: string; +} + +export interface ClientConnection { + readonly onData: Event; + readonly onClose: Event; + send(data: ArrayBuffer): void; +} + +export const forward = (connectionUrl: string): Promise => { + return new Promise((resolve, reject): void => { + const socket = new WebSocket(connectionUrl); + const closeEmitter = new Emitter(); + const dataEmitter = new Emitter(); + const connection: ClientConnection = { + get onClose(): Event { + return closeEmitter.event; + }, + get onData(): Event { + return dataEmitter.event; + }, + send(data: ArrayBuffer): void { + socket.send(data); + }, + }; + socket.binaryType = "arraybuffer"; + socket.addEventListener("message", (event) => { + dataEmitter.emit(event.data); + }); + socket.addEventListener("error", (event) => { + reject("uncertain"); + }); + socket.addEventListener("open", () => { + resolve(connection); + }); + socket.addEventListener("close", (event) => { + closeEmitter.emit({ + code: event.code, + reason: event.reason, + }); + }); + }); +}; diff --git a/packages/tunnel/src/common.ts b/packages/tunnel/src/common.ts new file mode 100644 index 00000000..9fa33a76 --- /dev/null +++ b/packages/tunnel/src/common.ts @@ -0,0 +1,5 @@ +export enum TunnelCloseCode { + Normal = 1000, + Error = 4000, + ConnectionRefused = 4001, +} diff --git a/packages/tunnel/src/server.ts b/packages/tunnel/src/server.ts new file mode 100644 index 00000000..5fcabe7b --- /dev/null +++ b/packages/tunnel/src/server.ts @@ -0,0 +1,53 @@ +import * as net from "net"; +import { TunnelCloseCode } from "./common"; + +export interface WS { + addEventListener(event: "message", cb: (event: { + // tslint:disable-next-line:no-any + readonly data: any; + }) => void): void; + addEventListener(event: "close", cb: () => void): void; + binaryType: string; + close(code: number, reason?: string): void; + // tslint:disable-next-line:no-any + send(data: any): void; +} + +export const handle = async (socket: WS, port: number): Promise => { + const hosts = [ + "127.0.0.1", + "::", // localhost + ]; + + let localSocket: net.Socket | undefined; + for (let i = 0; i < hosts.length; i++) { + if (localSocket) { + break; + } + localSocket = await new Promise((resolve, reject): void => { + const socket = net.connect({ + host: hosts[i], + port, + }, () => { + // Connected + resolve(socket); + }); + socket.on("error", (err: Error & { readonly code: string }) => { + if (err.code === "ECONNREFUSED") { + resolve(undefined); + } + }); + }); + } + if (!localSocket) { + socket.close(TunnelCloseCode.ConnectionRefused); + + return; + } + socket.binaryType = "arraybuffer"; + socket.addEventListener("message", (event) => localSocket!.write(Buffer.from(event.data))); + socket.addEventListener("close", () => localSocket!.end()); + localSocket.on("data", (data) => socket.send(data)); + localSocket.on("error", (err) => socket.close(TunnelCloseCode.Error, err.toString())); + localSocket.on("close", () => socket.close(TunnelCloseCode.Normal)); +}; diff --git a/packages/vscode/src/client.ts b/packages/vscode/src/client.ts index fd51d2fd..0a489dac 100644 --- a/packages/vscode/src/client.ts +++ b/packages/vscode/src/client.ts @@ -2,6 +2,7 @@ import * as paths from "./fill/paths"; import "./fill/platform"; import "./fill/storageDatabase"; import "./fill/windowsService"; +import "./fill/workspacesService"; import "./fill/environmentService"; import "./fill/vscodeTextmate"; import "./fill/codeEditor"; @@ -17,13 +18,14 @@ import { LogLevel } from "vs/platform/log/common/log"; import { URI } from "vs/base/common/uri"; import { INotificationService } from "vs/platform/notification/common/notification"; import { IProgressService2, ProgressLocation } from "vs/platform/progress/common/progress"; -import { ExplorerItem, Model } from "vs/workbench/parts/files/common/explorerModel"; +import { ExplorerItem, ExplorerModel } from "vs/workbench/parts/files/common/explorerModel"; import { DragMouseEvent } from "vs/base/browser/mouseEvent"; import { IEditorService, IResourceEditor } from "vs/workbench/services/editor/common/editorService"; import { IEditorGroup } from "vs/workbench/services/group/common/editorGroupsService"; -import { IWindowsService } from "vs/platform/windows/common/windows"; +import { IWindowsService, IWindowConfiguration } from "vs/platform/windows/common/windows"; import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection"; import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey"; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces"; export class Client extends IdeClient { private readonly windowId = parseInt(new Date().toISOString().replace(/[-:.TZ]/g, ""), 10); @@ -39,7 +41,7 @@ export class Client extends IdeClient { return this._builtInExtensionsDirectory; } - public async handleExternalDrop(target: ExplorerItem | Model, originalEvent: DragMouseEvent): Promise { + public async handleExternalDrop(target: ExplorerItem | ExplorerModel, originalEvent: DragMouseEvent): Promise { await this.upload.uploadDropped( originalEvent.browserEvent as DragEvent, (target instanceof ExplorerItem ? target : target.roots[0]).resource, @@ -93,6 +95,25 @@ export class Client extends IdeClient { return new PasteAction(); } + public set workspace(ws: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined) { + if (typeof ws === "undefined") { + window.localStorage.removeItem("workspace"); + } else { + window.localStorage.setItem("workspace", JSON.stringify(ws)); + } + + location.reload(); + } + + public get workspace(): undefined | IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier { + const ws = window.localStorage.getItem("workspace"); + try { + return JSON.parse(ws!); + } catch (ex) { + return undefined; + } + } + public get serviceCollection(): ServiceCollection { if (!this._serviceCollection) { throw new Error("Trying to access service collection before it has been set"); @@ -162,8 +183,9 @@ export class Client extends IdeClient { this._builtInExtensionsDirectory = data.builtInExtensionsDirectory; process.env.SHELL = data.shell; - const { startup } = require("./startup"); - await startup({ + const workspace = this.workspace || URI.file(data.workingDirectory); + const { startup } = require("./startup") as typeof import("vs/workbench/electron-browser/main"); + const config: IWindowConfiguration = { machineId: "1", windowId: this.windowId, logLevel: LogLevel.Info, @@ -174,9 +196,13 @@ export class Client extends IdeClient { nodeCachedDataDir: data.tmpDirectory, perfEntries: [], _: [], - folderUri: URI.file(data.workingDirectory), - }); - + }; + if ((workspace as IWorkspaceIdentifier).configPath) { + config.workspace = workspace as IWorkspaceIdentifier; + } else { + config.folderUri = workspace as URI; + } + await startup(config); const contextKeys = this.serviceCollection.get(IContextKeyService) as IContextKeyService; const bounded = this.clipboardContextKey.bindTo(contextKeys); this.clipboard.onPermissionChange((enabled) => { diff --git a/packages/vscode/src/dialog.scss b/packages/vscode/src/dialog.scss new file mode 100644 index 00000000..936c28c3 --- /dev/null +++ b/packages/vscode/src/dialog.scss @@ -0,0 +1,146 @@ +.dialog { + --primary: #2A2E37; + --border: black; + --faded: #a0a1a5; + --header-background: #161616; + --header-foreground: white; + --list-active-selection-background: rgb(0, 120, 160); + --list-active-selection-foreground: white; + --list-hover-background: rgb(36, 39, 46); + font-family: inherit; + box-shadow: 0 18px 80px 10px rgba(0, 0, 0, 0.1); + background-color: var(--primary); + display: flex; + flex-direction: column; + user-select: none; + overflow: hidden; + border-radius: 5px; + + .monaco-tl-twistie { + display: none; + } + + .title { + background-color: var(--header-background); + color: var(--header-foreground); + padding: 1px; + font-size: 11px; + font-weight: normal; + text-transform: uppercase; + white-space: nowrap; + padding-left: 10px; + } + + .nav { + display: flex; + flex-direction: row; + padding: 4px; + padding-top: 8px; + padding-bottom: 8px; + border-bottom: 1px solid var(--border); + min-height: 32px; + } + + .path { + display: flex; + flex-direction: row; + + .path-part { + padding: 5px; + border-radius: 3px; + font-size: 1.2em; + cursor: pointer; + + &:not(:first-child) { + margin-left: 5px; + } + + &.active { + font-weight: bold; + color: var(--list-active-selection-foreground); + } + } + } + + .file-area { + flex: 1; + display: flex; + flex-direction: column; + + .dialog-grid { + display: grid; + grid-template-columns: 2fr 0.2fr 0.8fr; + } + + .headings { + padding: 8px; + font-size: 12px; + } + + .dialog-entry { + cursor: pointer; + font-size: 1.2em; + padding: 0px; + padding-left: 8px; + padding-right: 8px; + + .dialog-entry-info { + display: flex; + flex-direction: row; + } + + .dialog-entry-icon { + width: 16px; + height: 19px; + display: inline-block; + margin-right: 5px; + margin-bottom: -3px; + } + + &:hover { + background-color: var(--list-hover-background); + } + + &.active { + background-color: var(--list-active-selection-background); + color: var(--list-active-selection-foreground); + } + } + } + + .buttons { + display: flex; + flex-direction: row; + padding: 10px; + position: relative; + background: var(--primary); + border-top: 1px solid var(--border); + + button:first-child { + margin-left: auto; + margin-right: 10px; + } + + button { + background: transparent; + outline: none; + border: 0; + color: var(--faded); + padding: 10px; + padding-left: 18px; + padding-right: 18px; + transition: 150ms background ease, 150ms color ease; + cursor: pointer; + border-radius: 5px; + + &:hover { + background: var(--titlebar); + color: white; + } + } + } +} + +.monaco-shell .monaco-tree.focused.no-focused-item:focus:before, .monaco-shell .monaco-list:not(.element-focused):focus:before { + display: none; +} \ No newline at end of file diff --git a/packages/vscode/src/dialog.ts b/packages/vscode/src/dialog.ts new file mode 100644 index 00000000..ec43851e --- /dev/null +++ b/packages/vscode/src/dialog.ts @@ -0,0 +1,471 @@ +import { Emitter, Event } from "@coder/events"; +import { client as ideClient } from "@coder/ide/src/fill/client"; +import { client } from "@coder/vscode/src/client"; +import * as fs from "fs"; +import * as path from "path"; +import { $, addClass, append } from "vs/base/browser/dom"; +import { HighlightedLabel } from "vs/base/browser/ui/highlightedlabel/highlightedLabel"; +import { ObjectTree } from "vs/base/browser/ui/tree/objectTree"; +import { ITreeElement, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from "vs/base/browser/ui/tree/tree"; +import { KeyCode } from "vs/base/common/keyCodes"; +import { URI } from "vs/base/common/uri"; +import { getIconClasses } from "vs/editor/common/services/getIconClasses"; +import { IModelService } from "vs/editor/common/services/modelService"; +import { IModeService } from "vs/editor/common/services/modeService"; +import { FileKind } from "vs/platform/files/common/files"; +import "./dialog.scss"; +import { IThemeService } from "vs/platform/theme/common/themeService"; + +declare var __non_webpack_require__: typeof require; + +export enum DialogType { + NewFolder, + Save, + Open, +} + +export interface CommonDialogOptions { + readonly title?: string; + readonly defaultPath?: string; + readonly buttonLabel?: string; +} + +export interface OpenDialogOptions extends CommonDialogOptions { + readonly properties: { + readonly openFile: true; + readonly openDirectory?: boolean; + readonly showHiddenFiles?: boolean; + } | { + readonly openDirectory: true; + readonly showHiddenFiles?: boolean; + readonly openFile?: boolean; + }; +} + +export interface SaveDialogOptions extends CommonDialogOptions { + readonly type: DialogType.Save; + readonly nameFieldLabel?: string; +} + +export type DialogOptions = OpenDialogOptions | SaveDialogOptions; + +export const showOpenDialog = (options: OpenDialogOptions): Promise => { + return new Promise((resolve, reject): void => { + const dialog = new Dialog(DialogType.Open, options); + dialog.onSelect((e) => { + dialog.dispose(); + resolve(e); + }); + dialog.onError((e) => { + dialog.dispose(); + reject(e); + }); + }); + +}; + +interface DialogEntry { + readonly fullPath: string; + readonly name: string; + readonly isDirectory: boolean; + readonly size: number; + readonly lastModified: string; +} + +class Dialog { + private _path: string | undefined; + + private static readonly UpperDirId = ".."; + + private readonly filesNode: HTMLElement; + private readonly pathNode: HTMLElement; + + private readonly entryList: ObjectTree; + private readonly background: HTMLElement; + private readonly root: HTMLElement; + + private readonly selectEmitter: Emitter; + private readonly errorEmitter: Emitter; + + public constructor( + private readonly type: DialogType, + private readonly options: DialogOptions, + ) { + this.selectEmitter = new Emitter(); + this.errorEmitter = new Emitter(); + + this.background = document.createElement("div"); + this.background.style.position = "absolute"; + this.background.style.top = "0"; + this.background.style.left = "0"; + this.background.style.bottom = "0"; + this.background.style.right = "0"; + this.background.style.zIndex = "5"; + this.background.style.display = "flex"; + this.background.style.alignItems = "center"; + this.background.style.justifyContent = "center"; + this.background.style.background = "rgba(0, 0, 0, 0.25)"; + + this.root = document.createElement("div"); + this.root.style.width = "850px"; + this.root.style.height = "600px"; + this.background.appendChild(this.root); + document.body.appendChild(this.background); + this.root.classList.add("dialog"); + + const setProperty = (vari: string, id: string): void => { + const getColor = (id: string): string | undefined => { + const ts = client.serviceCollection.get(IThemeService) as IThemeService; + const c = ts.getTheme().getColor(id); + if (!c) { + return; + } + + return c.toString(); + }; + const c = getColor(id); + if (c) { + this.root.style.setProperty(vari, c); + } + }; + setProperty("--primary", "sideBar.background"); + setProperty("--list-active-selection-background", "list.activeSelectionBackground"); + setProperty("--list-active-selection-foreground", "list.activeSelectionForeground"); + setProperty("--list-hover-background", "list.hoverBackground"); + setProperty("--header-background", "sideBarSectionHeader.background"); + setProperty("--header-foreground", "sideBarSectionHeader.foreground"); + setProperty("--border", "panel.border"); + + this.background.addEventListener("contextmenu", (event) => { + event.preventDefault(); + }); + + const titleNode = document.createElement("div"); + titleNode.classList.add("title"); + let title: string | undefined; + switch (this.type) { + // case DialogType.NewFolder: + // title = "New Folder"; + // break; + case DialogType.Open: + title = "Open File"; + break; + case DialogType.Save: + title = "Save File"; + break; + default: + throw new Error("Uncased type"); + } + titleNode.innerText = options.title || title; + this.root.appendChild(titleNode); + + const navItems = document.createElement("div"); + navItems.classList.add("nav"); + + this.pathNode = document.createElement("div"); + this.pathNode.classList.add("path"); + navItems.appendChild(this.pathNode); + this.root.appendChild(navItems); + + const fileAreaNode = document.createElement("div"); + fileAreaNode.classList.add("file-area"); + fileAreaNode.classList.add("show-file-icons"); + + const headingsNode = document.createElement("div"); + headingsNode.className = "headings dialog-grid"; + ["Name", "Size", "Last Modified"].forEach(e => { + const header = document.createElement("div"); + header.innerText = e; + headingsNode.appendChild(header); + }); + + this.filesNode = document.createElement("div"); + this.filesNode.className = "files-list"; + fileAreaNode.appendChild(headingsNode); + this.entryList = new ObjectTree(this.filesNode, { + getHeight: (entry: DialogEntry): number => { + return 20; + }, + getTemplateId: (entry: DialogEntry): string => { + return "dialog-entry"; + }, + }, [new DialogEntryRenderer()], { + openController: { + shouldOpen: (event): boolean => { + return true; + }, + }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (element): string => { + return element.name; + }, + mightProducePrintableCharacter: (event): boolean => { + if (event.ctrlKey || event.metaKey) { + // ignore ctrl/cmd-combination but not shift/alt-combinatios + return false; + } + // weak check for certain ranges. this is properly implemented in a subclass + // with access to the KeyboardMapperFactory. + if ((event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z) + || (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9) + || event.keyCode === KeyCode.US_DOT || event.keyCode === KeyCode.US_SLASH || event.keyCode === KeyCode.US_MINUS) { + return true; + } + + return false; + }, + }, + automaticKeyboardNavigation: true, + enableKeyboardNavigation: true, + multipleSelectionSupport: false, + openOnSingleClick: false, + filter: { + filter: (): TreeFilterResult => { + // tslint:disable-next-line:no-any + (this.entryList)._options.simpleKeyboardNavigation = true; + // tslint:disable-next-line:no-any + const pat = (this.entryList).typeFilterController.filter._pattern; + + return { + data: pat, + visibility: TreeVisibility.Visible, + }; + }, + }, + filterOnType: true, + }); + // tslint:disable-next-line:no-any + (this.entryList).focusNavigationFilter = (node: ITreeNode): boolean => { + if (node.filterData) { + return node.element.name.toLowerCase().startsWith(node.filterData.toLowerCase()!); + } + + return false; + }; + this.entryList.onDidOpen((event) => { + const element = event.elements[0]!; + if (!element) { + const fv = this.filterValue; + + if (fv === Dialog.UpperDirId) { + this.path = path.dirname(this._path!); + } + + if (fv.startsWith("/")) { + fs.stat(fv, (err, stats) => { + if (err) { + return; + } + + if (stats.isDirectory()) { + this.path = fv; + } + }); + } + + return; + } + if (element.isDirectory) { + this.path = element.fullPath; + } else { + // Open + this.selectEmitter.emit(element.fullPath); + } + }); + fileAreaNode.appendChild(this.entryList.getHTMLElement()); + this.root.appendChild(fileAreaNode); + + const buttonsNode = document.createElement("div"); + buttonsNode.className = "buttons"; + const cancelBtn = document.createElement("button"); + cancelBtn.innerText = "Cancel"; + cancelBtn.addEventListener("click", () => { + this.errorEmitter.emit(new Error("Cancelled")); + }); + buttonsNode.appendChild(cancelBtn); + const confirmBtn = document.createElement("button"); + confirmBtn.innerText = "Confirm"; + confirmBtn.addEventListener("click", () => { + if (this._path) { + this.selectEmitter.emit(this._path); + } + }); + buttonsNode.appendChild(confirmBtn); + this.root.appendChild(buttonsNode); + this.entryList.layout(); + + this.path = options.defaultPath || "/"; + } + + public get onSelect(): Event { + return this.selectEmitter.event; + } + + public get onError(): Event { + return this.errorEmitter.event; + } + + public dispose(): void { + this.selectEmitter.dispose(); + this.errorEmitter.dispose(); + this.entryList.dispose(); + this.background.remove(); + } + + private buildPath(): void { + while (this.pathNode.lastChild) { + this.pathNode.removeChild(this.pathNode.lastChild); + } + + if (!this._path) { + throw new Error("cannot build path node without valid path"); + } + + const pathParts = ["", ...this._path.split("/").filter((p) => p.length > 0)]; + + for (let i = 0; i < pathParts.length; i++) { + const pathPartNode = document.createElement("div"); + pathPartNode.classList.add("path-part"); + pathPartNode.innerText = pathParts[i].length > 0 ? pathParts[i] : "/"; + + if (i === pathParts.length - 1) { + pathPartNode.classList.add("active"); + } + + pathPartNode.addEventListener("click", () => { + this.path = "/" + pathParts.slice(0, i + 1).join("/"); + }); + + this.pathNode.appendChild(pathPartNode); + } + } + + private set path(directory: string) { + const ts = Date.now(); + this.list(directory).then((value) => { + this._path = directory; + this.buildPath(); + + while (this.filesNode.lastChild) { + this.filesNode.removeChild(this.filesNode.lastChild); + } + + const items = value.filter((v) => { + if (v.name.startsWith(".")) { + const props = (this.options as OpenDialogOptions).properties; + if (props && props.showHiddenFiles) { + return true; + } + + return false; + } + + return true; + }); + + this.entryList.setChildren(null, items.map((i: DialogEntry): ITreeElement => ({ element: i }))); + this.entryList.domFocus(); + this.entryList.setFocus([null]); + // Clears the input on refresh + // tslint:disable-next-line:no-any + (this.entryList).typeFilterController.onInput(""); + }).catch((ex) => { + this.errorEmitter.emit(ex); + }); + } + + private get filterValue(): string { + // tslint:disable-next-line:no-any + return (this.entryList).typeFilterController.filter._pattern; + } + + private async list(directory: string): Promise> { + return ideClient.evaluate((directory) => { + const fs = __non_webpack_require__("fs") as typeof import("fs"); + const util = __non_webpack_require__("util") as typeof import("util"); + const path = __non_webpack_require__("path") as typeof import("path"); + + return util.promisify(fs.readdir)(directory).then((paths) => { + paths = paths.sort(); + + return Promise.all(paths.map(p => util.promisify(fs.stat)(path.join(directory, p)))).then((stats) => { + return { + paths, + stats, + }; + }); + }).then(({ paths, stats }) => { + return stats.map((stat, index): DialogEntry => { + return { + fullPath: path.join(directory, paths[index]), + name: paths[index], + isDirectory: stat.isDirectory(), + lastModified: stat.mtime.toDateString(), + size: stat.size, + }; + }); + }); + }, directory); + } +} + +interface DialogEntryData { + icon: HTMLElement; + size: HTMLElement; + lastModified: HTMLElement; + label: HighlightedLabel; +} + +class DialogEntryRenderer implements ITreeRenderer { + public get templateId(): string { + return "dialog-entry"; + } + + public renderTemplate(container: HTMLElement): DialogEntryData { + addClass(container, "dialog-entry"); + addClass(container, "dialog-grid"); + + const wrapper = append(container, $(".dialog-entry-info")); + const icon: HTMLElement = append(wrapper, $("div")); + const name = append(wrapper, $(".dialog-entry-name")); + const label = new HighlightedLabel(name, false); + append(container, wrapper); + const size = append(container, $(".dialog-entry-size")); + const mtime = append(container, $(".dialog-entry-mtime")); + + return { + icon, + size, + lastModified: mtime, + label, + }; + } + + public renderElement(node: ITreeNode, index: number, templateData: DialogEntryData): void { + templateData.icon.className = "dialog-entry-icon monaco-icon-label"; + const classes = getIconClasses( + client.serviceCollection.get(IModelService) as IModelService, + client.serviceCollection.get(IModeService) as IModeService, + URI.file(node.element.name), + node.element.isDirectory ? FileKind.FOLDER : FileKind.FILE, + ); + templateData.icon.hidden = classes.length === 0; + classes.forEach((c) => { + try { + templateData.icon.classList.add(c); + } catch (ex) { + // Nothin needed. Sometimes bad classes are given + } + }); + templateData.label.set(node.element.name, typeof node.filterData === "string" && node.element.name.toLowerCase().startsWith(node.filterData.toLowerCase()) ? [{ + start: 0, + end: node.filterData.length, + }] : []); + templateData.size.innerText = node.element.size.toString(); + templateData.lastModified.innerText = node.element.lastModified; + } + + public disposeTemplate(templateData: DialogEntryData): void { + // throw new Error("Method not implemented."); + } +} diff --git a/packages/vscode/src/fill/menuRegistry.ts b/packages/vscode/src/fill/menuRegistry.ts index c1f41f47..28cd4f44 100644 --- a/packages/vscode/src/fill/menuRegistry.ts +++ b/packages/vscode/src/fill/menuRegistry.ts @@ -1,7 +1,7 @@ import { logger } from "@coder/logger"; import { IDisposable } from "vs/base/common/lifecycle"; import * as actions from "vs/platform/actions/common/actions"; -import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions"; +import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions/developerActions"; // Intercept appending menu items so we can skip items that won't work. const originalAppend = actions.MenuRegistry.appendMenuItem.bind(actions.MenuRegistry); diff --git a/packages/vscode/src/fill/storageDatabase.ts b/packages/vscode/src/fill/storageDatabase.ts index e37670e9..ec085dd4 100644 --- a/packages/vscode/src/fill/storageDatabase.ts +++ b/packages/vscode/src/fill/storageDatabase.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile } from "fs"; +import { readFile, writeFile, mkdir } from "fs"; import * as path from "path"; import { promisify } from "util"; import { IDisposable } from "@coder/disposable"; @@ -7,6 +7,8 @@ import * as workspaceStorage from "vs/base/node/storage"; import * as globalStorage from "vs/platform/storage/node/storageIpc"; import * as paths from "./paths"; import { logger, field } from "@coder/logger"; +import { client } from "@coder/vscode/src/client"; +import { IStorageService, WillSaveStateReason } from "vs/platform/storage/common/storage"; class StorageDatabase implements workspaceStorage.IStorageDatabase { public readonly onDidChangeItemsExternal = Event.None; @@ -20,11 +22,9 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase { if (!navigator.sendBeacon) { throw new Error("cannot save state"); } - // TODO: Need to use navigator.sendBeacon instead of the web socket, or we - // need to save when there is a change. Should we save as a sqlite3 - // database instead of JSON? Could send to the server the way the global - // storage works. Or maybe fill `vscode-sqlite3` to do that. - this.save(); + + this.triggerFlush(WillSaveStateReason.SHUTDOWN); + navigator.sendBeacon(`/resource${this.path}`, this.content); }); } @@ -58,7 +58,7 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase { request.delete.forEach(key => this.items.delete(key)); } - return Promise.resolve(); + return this.save(); } public close(): Promise { @@ -69,13 +69,38 @@ class StorageDatabase implements workspaceStorage.IStorageDatabase { return Promise.resolve("ok"); } - private save(): Promise { + private async save(): Promise { + try { + await promisify(mkdir)(path.dirname(this.path)); + } catch (ex) {} + + return promisify(writeFile)(this.path, this.content); + } + + private triggerFlush(reason: WillSaveStateReason = WillSaveStateReason.NONE): boolean { + // tslint:disable-next-line:no-any + const storageService = client.serviceCollection.get(IStorageService) as any; + if (reason === WillSaveStateReason.SHUTDOWN && storageService.close) { + storageService.close(); + + return true; + } + if (storageService._onWillSaveState) { + storageService._onWillSaveState.fire({ reason }); + + return true; + } + + return false; + } + + private get content(): string { const json: { [key: string]: string } = {}; this.items.forEach((value, key) => { json[key] = value; }); - return promisify(writeFile)(this.path, JSON.stringify(json)); + return JSON.stringify(json); } } diff --git a/packages/vscode/src/fill/windowsService.ts b/packages/vscode/src/fill/windowsService.ts index b2228817..04d545a7 100644 --- a/packages/vscode/src/fill/windowsService.ts +++ b/packages/vscode/src/fill/windowsService.ts @@ -1,13 +1,14 @@ import * as electron from "electron"; import { Emitter } from "@coder/events"; import * as windowsIpc from "vs/platform/windows/node/windowsIpc"; -import { IWindowsService, INativeOpenDialogOptions, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IMessageBoxResult, IDevToolsOptions, IEnterWorkspaceResult, CrashReporterStartOptions, INewWindowOptions } from "vs/platform/windows/common/windows"; +import { IWindowsService, INativeOpenDialogOptions, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IMessageBoxResult, IDevToolsOptions, IEnterWorkspaceResult, CrashReporterStartOptions, INewWindowOptions, IOpenFileRequest, IAddFoldersRequest } from "vs/platform/windows/common/windows"; import { ParsedArgs } from "vs/platform/environment/common/environment"; import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces"; import { URI } from "vs/base/common/uri"; import { IRecentlyOpened } from "vs/platform/history/common/history"; import { ISerializableCommandAction } from "vs/platform/actions/common/actions"; import { client } from "../client"; +import { showOpenDialog } from "../dialog"; /** * Instead of going to the shared process, we'll directly run these methods on @@ -34,20 +35,70 @@ class WindowsService implements IWindowsService { private readonly window = new electron.BrowserWindow(); // Dialogs - public pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - throw new Error("not implemented"); + public async pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { + showOpenDialog({ + ...(_options.dialogOptions || {}), + properties: { + openFile: true, + openDirectory: true, + }, + }).then((path) => { + // tslint:disable-next-line:no-any + (electron.ipcMain).send("vscode:openFiles", { + filesToOpen: [{ + fileUri: URI.file(path), + }], + } as IOpenFileRequest); + }).catch((ex) => { + // + }); } - public pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { - throw new Error("not implemented"); + public async pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { + showOpenDialog({ + ...(_options.dialogOptions || {}), + properties: { + openFile: true, + }, + }).then((path) => { + // tslint:disable-next-line:no-any + (electron.ipcMain).send("vscode:openFiles", { + filesToOpen: [{ + fileUri: URI.file(path), + }], + } as IOpenFileRequest); + }).catch((ex) => { + // + }); } - public pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - throw new Error("not implemented"); + public async pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { + showOpenDialog({ + ...(_options.dialogOptions || {}), + properties: { + openDirectory: true, + }, + }).then((path) => { + client.workspace = URI.file(path); + }).catch((ex) => { + // + }); } - public pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { - throw new Error("not implemented"); + public async pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { + showOpenDialog({ + ...(_options.dialogOptions || {}), + properties: { + openDirectory: true, + }, + }).then((path) => { + // tslint:disable-next-line:no-any + (electron.ipcMain).send("vscode:addFolders", { + foldersToAdd: [URI.file(path)], + } as IAddFoldersRequest); + }).catch((ex) => { + // + }); } public showMessageBox(windowId: number, options: MessageBoxOptions): Promise { @@ -70,10 +121,14 @@ class WindowsService implements IWindowsService { } public showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { - return new Promise((resolve): void => { - electron.dialog.showOpenDialog(this.getWindowById(windowId), options, (filePaths, _bookmarks) => { - resolve(filePaths); - }); + return showOpenDialog({ + ...(options || {}), + properties: { + openDirectory: true, + openFile: true, + }, + }).then((path) => { + return [path]; }); } @@ -93,8 +148,17 @@ class WindowsService implements IWindowsService { throw new Error("not implemented"); } - public enterWorkspace(_windowId: number, _path: string): Promise { - throw new Error("not implemented"); + public enterWorkspace(_windowId: number, _path: URI): Promise { + if (_path.path.endsWith(".json")) { + client.workspace = { + id: "Untitled", + configPath: _path.path, + }; + } else { + client.workspace = _path; + } + + return undefined!; } public createAndEnterWorkspace(_windowId: number, _folders?: IWorkspaceFolderCreationData[], _path?: string): Promise { @@ -251,8 +315,8 @@ class WindowsService implements IWindowsService { return Promise.resolve(1); } - public openExternal(_url: string): Promise { - throw new Error("not implemented"); + public async openExternal(_url: string): Promise { + return typeof window.open(_url, "_blank") !== "undefined"; } public startCrashReporter(_config: CrashReporterStartOptions): Promise { diff --git a/packages/vscode/src/fill/workbenchRegistry.ts b/packages/vscode/src/fill/workbenchRegistry.ts index 20211b8b..c0a5af9a 100644 --- a/packages/vscode/src/fill/workbenchRegistry.ts +++ b/packages/vscode/src/fill/workbenchRegistry.ts @@ -4,7 +4,7 @@ import { Registry } from "vs/platform/registry/common/platform"; import { IWorkbenchActionRegistry, Extensions } from "vs/workbench/common/actions"; import { SyncActionDescriptor } from "vs/platform/actions/common/actions"; import { ContextKeyExpr } from "vs/platform/contextkey/common/contextkey"; -import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions"; +import { ToggleDevToolsAction } from "vs/workbench/electron-browser/actions/developerActions"; import { TerminalPasteAction } from "vs/workbench/parts/terminal/electron-browser/terminalActions"; import { KEYBINDING_CONTEXT_TERMINAL_FOCUS } from "vs/workbench/parts/terminal/common/terminal"; import { KeyCode, KeyMod } from "vs/base/common/keyCodes"; diff --git a/packages/vscode/src/fill/workspacesService.ts b/packages/vscode/src/fill/workspacesService.ts new file mode 100644 index 00000000..904d2686 --- /dev/null +++ b/packages/vscode/src/fill/workspacesService.ts @@ -0,0 +1,40 @@ +import { URI } from "vs/base/common/uri"; +import { IEnvironmentService } from "vs/platform/environment/common/environment"; +import { ILogService } from "vs/platform/log/common/log"; +import { IWorkspaceFolderCreationData, IWorkspaceIdentifier, IWorkspacesService } from "vs/platform/workspaces/common/workspaces"; +import { WorkspacesMainService } from "vs/platform/workspaces/electron-main/workspacesMainService"; +import * as workspacesIpc from "vs/platform/workspaces/node/workspacesIpc"; +import { client } from "../client"; + +/** + * Instead of going to the shared process, we'll directly run these methods on + * the client. This setup means we can only control the current window. + */ +class WorkspacesService implements IWorkspacesService { + // tslint:disable-next-line:no-any + public _serviceBrand: any; + + public createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[] | undefined): Promise { + const mainService = new WorkspacesMainService( + client.serviceCollection.get(IEnvironmentService) as IEnvironmentService, + client.serviceCollection.get(ILogService) as ILogService, + ); + + // lib/vscode/src/vs/platform/workspaces/node/workspacesIpc.ts + const rawFolders: IWorkspaceFolderCreationData[] = folders!; + if (Array.isArray(rawFolders)) { + folders = rawFolders.map(rawFolder => { + return { + uri: URI.revive(rawFolder.uri), // convert raw URI back to real URI + name: rawFolder.name!, + } as IWorkspaceFolderCreationData; + }); + } + + return mainService.createUntitledWorkspace(folders); + } +} + +const target = workspacesIpc as typeof workspacesIpc; +// @ts-ignore TODO: don't ignore it. +target.WorkspacesChannelClient = WorkspacesService; diff --git a/packages/vscode/src/startup.ts b/packages/vscode/src/startup.ts index 26f3b881..e088b295 100644 --- a/packages/vscode/src/startup.ts +++ b/packages/vscode/src/startup.ts @@ -5,144 +5,176 @@ import "vs/loader"; -// Base -import "vs/base/common/strings"; -import "vs/base/common/errors"; +//#region --- workbench/editor core -// Configuration -import "vs/workbench/services/configuration/common/configurationExtensionPoint"; - -// Editor import "vs/editor/editor.all"; -// Platform -import "vs/platform/widget/browser/contextScopedHistoryWidget"; -import "vs/platform/label/electron-browser/label.contribution"; +import "vs/workbench/api/electron-browser/extensionHost.contribution"; -// Menus/Actions -import "vs/workbench/services/actions/electron-browser/menusExtensionPoint"; +import "vs/workbench/electron-browser/shell.contribution"; +import "vs/workbench/browser/workbench.contribution"; -// Views -import "vs/workbench/api/browser/viewsContainersExtensionPoint"; +import { startup } from "vs/workbench/electron-browser/main"; + +//#endregion + +//#region --- workbench actions + +import "vs/workbench/browser/actions/layoutActions"; +import "vs/workbench/browser/actions/listCommands"; +import "vs/workbench/browser/actions/navigationActions"; +import "vs/workbench/browser/parts/quickopen/quickOpenActions"; +import "vs/workbench/browser/parts/quickinput/quickInputActions"; + +//#endregion + +//#region --- API Extension Points + +import "vs/workbench/api/common/menusExtensionPoint"; +import "vs/workbench/api/common/configurationExtensionPoint"; import "vs/workbench/api/browser/viewsExtensionPoint"; +//#endregion + +//#region --- workbench services + +import "vs/workbench/services/bulkEdit/electron-browser/bulkEditService"; + +//#endregion + +//#region --- workbench parts + // Localizations import "vs/workbench/parts/localizations/electron-browser/localizations.contribution"; -// Workbench -import "vs/workbench/browser/actions/toggleActivityBarVisibility"; -import "vs/workbench/browser/actions/toggleStatusbarVisibility"; -import "vs/workbench/browser/actions/toggleSidebarVisibility"; -import "vs/workbench/browser/actions/toggleSidebarPosition"; -import "vs/workbench/browser/actions/toggleEditorLayout"; -import "vs/workbench/browser/actions/toggleZenMode"; -import "vs/workbench/browser/actions/toggleCenteredLayout"; -import "vs/workbench/browser/actions/toggleTabsVisibility"; +// Preferences import "vs/workbench/parts/preferences/electron-browser/preferences.contribution"; import "vs/workbench/parts/preferences/browser/keybindingsEditorContribution"; + +// Logs import "vs/workbench/parts/logs/electron-browser/logs.contribution"; -import "vs/workbench/browser/parts/quickopen/quickopen.contribution"; +// Quick Open Handlers import "vs/workbench/parts/quickopen/browser/quickopen.contribution"; -import "vs/workbench/browser/parts/editor/editorPicker"; -import "vs/workbench/browser/parts/quickinput/quickInput.contribution"; +// Explorer import "vs/workbench/parts/files/electron-browser/explorerViewlet"; import "vs/workbench/parts/files/electron-browser/fileActions.contribution"; import "vs/workbench/parts/files/electron-browser/files.contribution"; +// Backup import "vs/workbench/parts/backup/common/backup.contribution"; +// Stats import "vs/workbench/parts/stats/node/stats.contribution"; +// Rapid Render Splash import "vs/workbench/parts/splash/electron-browser/partsSplash.contribution"; +// Search import "vs/workbench/parts/search/electron-browser/search.contribution"; import "vs/workbench/parts/search/browser/searchView"; import "vs/workbench/parts/search/browser/openAnythingHandler"; +// SCM import "vs/workbench/parts/scm/electron-browser/scm.contribution"; import "vs/workbench/parts/scm/electron-browser/scmViewlet"; +// Debug import "vs/workbench/parts/debug/electron-browser/debug.contribution"; -// import "vs/workbench/parts/debug/browser/debugQuickOpen"; -// import "vs/workbench/parts/debug/electron-browser/repl"; -// import "vs/workbench/parts/debug/browser/debugViewlet"; +import "vs/workbench/parts/debug/browser/debugQuickOpen"; +import "vs/workbench/parts/debug/electron-browser/repl"; +import "vs/workbench/parts/debug/browser/debugViewlet"; +// Markers import "vs/workbench/parts/markers/electron-browser/markers.contribution"; + +// Comments import "vs/workbench/parts/comments/electron-browser/comments.contribution"; +// HTML Preview import "vs/workbench/parts/html/electron-browser/html.contribution"; +// URL Support import "vs/workbench/parts/url/electron-browser/url.contribution"; + +// Webview import "vs/workbench/parts/webview/electron-browser/webview.contribution"; -import "vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution"; - +// Extensions Management import "vs/workbench/parts/extensions/electron-browser/extensions.contribution"; import "vs/workbench/parts/extensions/browser/extensionsQuickOpen"; import "vs/workbench/parts/extensions/electron-browser/extensionsViewlet"; -import "vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution"; - +// Output Panel import "vs/workbench/parts/output/electron-browser/output.contribution"; import "vs/workbench/parts/output/browser/outputPanel"; +// Terminal import "vs/workbench/parts/terminal/electron-browser/terminal.contribution"; import "vs/workbench/parts/terminal/browser/terminalQuickOpen"; import "vs/workbench/parts/terminal/electron-browser/terminalPanel"; -import "vs/workbench/electron-browser/workbench"; - -// import "vs/workbench/parts/relauncher/electron-browser/relauncher.contribution"; +// Relauncher +import "vs/workbench/parts/relauncher/electron-browser/relauncher.contribution"; +// Tasks import "vs/workbench/parts/tasks/electron-browser/task.contribution"; +// Emmet import "vs/workbench/parts/emmet/browser/emmet.browser.contribution"; import "vs/workbench/parts/emmet/electron-browser/emmet.contribution"; -import "vs/workbench/parts/codeEditor/codeEditor.contribution"; +// CodeEditor Contributions +import "vs/workbench/parts/codeEditor/electron-browser/codeEditor.contribution"; +// Execution import "vs/workbench/parts/execution/electron-browser/execution.contribution"; +// Snippets import "vs/workbench/parts/snippets/electron-browser/snippets.contribution"; import "vs/workbench/parts/snippets/electron-browser/snippetsService"; import "vs/workbench/parts/snippets/electron-browser/insertSnippet"; import "vs/workbench/parts/snippets/electron-browser/configureSnippets"; import "vs/workbench/parts/snippets/electron-browser/tabCompletion"; -import "vs/workbench/parts/themes/electron-browser/themes.contribution"; - -// import "vs/workbench/parts/feedback/electron-browser/feedback.contribution"; - -import "vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution"; +// Send a Smile +import "vs/workbench/parts/feedback/electron-browser/feedback.contribution"; +// Update import "vs/workbench/parts/update/electron-browser/update.contribution"; -// import "vs/workbench/parts/surveys/electron-browser/nps.contribution"; -// import "vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution"; +// Surveys +import "vs/workbench/parts/surveys/electron-browser/nps.contribution"; +import "vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution"; +// Performance import "vs/workbench/parts/performance/electron-browser/performance.contribution"; -// import "vs/workbench/parts/cli/electron-browser/cli.contribution"; +// CLI +import "vs/workbench/parts/cli/electron-browser/cli.contribution"; -import "vs/workbench/api/electron-browser/extensionHost.contribution"; - -import "vs/workbench/electron-browser/main.contribution"; -import { startup } from "vs/workbench/electron-browser/main"; - -// import "vs/workbench/parts/themes/test/electron-browser/themes.test.contribution"; +// Themes Support +import "vs/workbench/parts/themes/electron-browser/themes.contribution"; +import "vs/workbench/parts/themes/test/electron-browser/themes.test.contribution"; +// Watermark import "vs/workbench/parts/watermark/electron-browser/watermark"; +// Welcome +import "vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution"; +import "vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution"; import "vs/workbench/parts/welcome/overlay/browser/welcomeOverlay"; +import "vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution"; +// Outline import "vs/workbench/parts/outline/electron-browser/outline.contribution"; -import "vs/workbench/services/bulkEdit/electron-browser/bulkEditService"; - +// Experiments import "vs/workbench/parts/experiments/electron-browser/experiments.contribution"; +//#endregion + import { URI } from "vs/base/common/uri"; export { diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..3d954fee --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +yarn task build:server:binary diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 4db4cae1..dc0aed01 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -1,8 +1,8 @@ diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts -index 457818a975..ad45ffe58a 100644 +index a342222d04..c731e82591 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts -@@ -196,0 +197,2 @@ async function handshake(configuration: ISharedProcessConfiguration): Promise(logService: ILogService, modulePath: string, acti +@@ -719 +719 @@ function loadCommonJSModule(logService: ILogService, modulePath: string, acti - r = require.__$__nodeRequire(modulePath); + r = (global as any).nativeNodeRequire(modulePath); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts -index 38bf337a61..a6ee664a20 100644 +index e73acff052..bd65fcbd6c 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -171 +171 @@ export class ResourcesDropHandler { - return; + return (require('vs/../../../../packages/vscode') as typeof import ('vs/../../../../packages/vscode')).client.handleDrop(event, resolveTargetGroup, afterDrop, targetIndex); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts -index a43d63aa51..438d0a8245 100644 +index 1e6333c731..0a7bdeeb9b 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts -@@ -150,7 +150,7 @@ function openWorkbench(configuration: IWindowConfiguration): Promise { +@@ -147,7 +147,7 @@ function openWorkbench(configuration: IWindowConfiguration): Promise { - (self).require.config({ - onError: err => { - if (err.errorCode === 'load') { @@ -103,27 +103,23 @@ index a43d63aa51..438d0a8245 100644 + // } + // }); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts -index ea348f3a04..ada0003fea 100644 +index c0aff6bd4b..4f3b1e3984 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts -@@ -48 +48 @@ const TextInputActions: IAction[] = [ -- new Action('editor.action.clipboardPasteAction', nls.localize('paste', "Paste"), null, true, () => document.execCommand('paste') && Promise.resolve(true)), +@@ -49 +49 @@ const TextInputActions: IAction[] = [ +- new Action('editor.action.clipboardPasteAction', nls.localize('paste', "Paste"), undefined, true, () => Promise.resolve(document.execCommand('paste'))), + (require('vs/../../../../packages/vscode') as typeof import ('vs/../../../../packages/vscode')).client.pasteAction, +@@ -263 +263 @@ export class ElectronWindow extends Themable { +- (window).open = function (url: string, target: string, features: string, replace: boolean): any { ++ (window).openInvalid = function (url: string, target: string, features: string, replace: boolean): any { diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts -index 35bc4a82b3..45e96001b6 100644 +index d82db96296..c9bd4e0325 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts -@@ -250,0 +251 @@ export class Workbench extends Disposable implements IPartService { +@@ -272,0 +273 @@ export class Workbench extends Disposable implements IPartService { + (require('vs/../../../../packages/vscode') as typeof import ('vs/../../../../packages/vscode')).client.serviceCollection = serviceCollection; -diff --git a/src/vs/workbench/node/extensionHostProcess.ts b/src/vs/workbench/node/extensionHostProcess.ts -index 8d182d18d9..69d51e1aea 100644 ---- a/src/vs/workbench/node/extensionHostProcess.ts -+++ b/src/vs/workbench/node/extensionHostProcess.ts -@@ -132 +132 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { ++ return (require('vs/../../../../packages/vscode') as typeof import ('vs/../../../../packages/vscode')).client.handleExternalDrop(target, originalEvent as any); diff --git a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts index 4015c9cd5d..bebdb25f6c 100644 --- a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts @@ -147,14 +143,62 @@ index 4015c9cd5d..bebdb25f6c 100644 + // This channel only seems to be used when loading the app and we skip all of that, so it is never actually created or written to. + // outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(join(environmentService.logsPath, `main.log`)), log: true }); diff --git a/src/vs/workbench/parts/output/common/outputLinkProvider.ts b/src/vs/workbench/parts/output/common/outputLinkProvider.ts -index 63437034c9..acd82c8375 100644 +index 6d9451401a..510f5131ce 100644 --- a/src/vs/workbench/parts/output/common/outputLinkProvider.ts +++ b/src/vs/workbench/parts/output/common/outputLinkProvider.ts @@ -77,0 +78,2 @@ export class OutputLinkProvider { -+ // TODO@coder: get this working. ++ + return Promise.resolve([]); +diff --git a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js b/src/vs/workbench/parts/webview/electron-browser/webview-pre.js +index 29593dc6b6..dd3d25098d 100644 +--- a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js ++++ b/src/vs/workbench/parts/webview/electron-browser/webview-pre.js +@@ -10 +10,19 @@ +- const ipcRenderer = require('electron').ipcRenderer; ++ const ipcRenderer = { ++ on: (channel, callback) => { ++ window.addEventListener("message", (event) => { ++ if (event.data.channel === channel) { ++ callback(event.data.channel, ...event.data.data); ++ } ++ }); ++ }, ++ sendToHost: (channel, ...args) => { ++ window.parent.postMessage({ ++ channel, ++ data: args, ++ id: document.body.id, ++ }, "*"); ++ }, ++ }; ++ const process = { ++ pid: undefined, ++ }; +@@ -22,7 +40,7 @@ +- require('electron').webFrame.registerURLSchemeAsPrivileged('vscode-resource', { +- secure: true, +- bypassCSP: false, +- allowServiceWorkers: false, +- supportFetchAPI: true, +- corsEnabled: true +- }); ++ // require('electron').webFrame.registerURLSchemeAsPrivileged('vscode-resource', { ++ // secure: true, ++ // bypassCSP: false, ++ // allowServiceWorkers: false, ++ // supportFetchAPI: true, ++ // corsEnabled: true ++ // }); +diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts +index 5d9110300f..ecdc40b0eb 100644 +--- a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts ++++ b/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts +@@ -261,0 +262,3 @@ export class WebviewElement extends Disposable { ++ Object.defineProperty(this._options, 'useSameOriginForRoot', { ++ value: true, ++ }); diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts -index 7b4e8721ac..96d612f940 100644 +index 147f05b246..fa89583203 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts @@ -31,6 +31,6 @@ export class WalkThroughContentProvider implements ITextModelContentProvider, IW @@ -193,15 +237,22 @@ index 7b4e8721ac..96d612f940 100644 - this.modelService.updateModel(codeEditorModel, content.value); + this.modelService.updateModel(codeEditorModel, content); diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts -index 4cb7a231f3..78c87d13f6 100644 +index 29cbfd65c4..1091dc3c8a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -33,0 +34 @@ function getSystemExtensionsRoot(): string { + return (require('vs/../../../../packages/vscode') as typeof import ('vs/../../../../packages/vscode')).client.builtInExtensionsDirectory; -diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts -index 5b4136989f..25ccc0fe9e 100644 ---- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts -+++ b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts -@@ -181 +181 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i -- return resources.joinPath(iconThemeDocumentLocationDirname, path); -+ return "/resource" + resources.joinPath(iconThemeDocumentLocationDirname, path).path; +diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts +index 5f2935618c..af5498d7ac 100644 +--- a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts ++++ b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts +@@ -132 +132 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise ({ }], }, }, { - test: /\.(js)/, + test: /\.(js|css)/, exclude: /test/, }, { test: /\.(txt|d\.ts|test.ts|perf.data.js|jxs|scpt|exe|sh|less)$/, @@ -61,7 +61,7 @@ module.exports = (options = {}) => ({ // pages or iframes so we don't need to include it here. Also excluding // markdown.css because even though it uses the file-loader as shown above // in the string replace, it's still making its way into the main CSS. - exclude: /test|code\/electron-browser\/.+\.css$|markdown.css$/, + exclude: /test|code\/electron-browser\/.+\.css$/, test: /\.s?css$/, // This is required otherwise it'll fail to resolve CSS in common. include: root, @@ -73,7 +73,7 @@ module.exports = (options = {}) => ({ loader: "sass-loader", }], }, { - test: /\.(svg|png|ttf|woff|eot)$/, + test: /\.(svg|png|ttf|woff|eot|woff2)$/, use: [{ loader: "file-loader", options: { diff --git a/tsconfig.json b/tsconfig.json index df8a3b2e..a71c2bc1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "module": "commonjs", "baseUrl": ".", "rootDir": ".", + "jsx": "react", "outDir": "dist", "declaration": true, "sourceMap": true, diff --git a/yarn.lock b/yarn.lock index a3459e75..45d802cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,6 +14,23 @@ dependencies: execa "^0.2.2" +"@types/fs-extra@^5.0.4": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" + integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A== + dependencies: + "@types/node" "*" + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/node@*": + version "11.9.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14" + integrity sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA== + "@types/node@^10.12.18": version "10.12.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" @@ -1231,6 +1248,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deepmerge@^2.0.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + default-gateway@^2.6.0: version "2.7.2" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f" @@ -1943,6 +1965,15 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -2835,6 +2866,13 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -5178,6 +5216,17 @@ ts-node@^7.0.1: source-map-support "^0.5.6" yn "^2.0.0" +tsconfig-paths@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.8.0.tgz#4e34202d5b41958f269cf56b01ed95b853d59f72" + integrity sha512-zZEYFo4sjORK8W58ENkRn9s+HmQFkkwydDG7My5s/fnfr2YYCaiyXe/HBUcIgU8epEKOXwiahOO+KZYjiXlWyQ== + dependencies: + "@types/json5" "^0.0.29" + deepmerge "^2.0.1" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" @@ -5298,6 +5347,11 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"