Merge pull request #2563 from cdr/proxy-path-passthrough-0bb9
pathProxy.ts: Implement --proxy-path-passthrough
This commit is contained in:
commit
28e98c0ee0
37
doc/FAQ.md
37
doc/FAQ.md
@ -15,6 +15,7 @@
|
||||
- [How do I securely access web services?](#how-do-i-securely-access-web-services)
|
||||
- [Sub-paths](#sub-paths)
|
||||
- [Sub-domains](#sub-domains)
|
||||
- [Why does the code-server proxy strip `/proxy/<port>` from the request path?](#why-does-the-code-server-proxy-strip-proxyport-from-the-request-path)
|
||||
- [Multi-tenancy](#multi-tenancy)
|
||||
- [Docker in code-server container?](#docker-in-code-server-container)
|
||||
- [How can I disable telemetry?](#how-can-i-disable-telemetry)
|
||||
@ -208,6 +209,42 @@ code-server --proxy-domain <domain>
|
||||
Now you can browse to `<port>.<domain>`. Note that this uses the host header so
|
||||
ensure your reverse proxy forwards that information if you are using one.
|
||||
|
||||
## Why does the code-server proxy strip `/proxy/<port>` from the request path?
|
||||
|
||||
HTTP servers should strive to use relative URLs to avoid needed to be coupled to the
|
||||
absolute path at which they are served. This means you must use trailing slashes on all
|
||||
paths with subpaths. See https://blog.cdivilly.com/2019/02/28/uri-trailing-slashes
|
||||
|
||||
This is really the "correct" way things work and why the striping of the base path is the
|
||||
default. If your application uses relative URLs and does not assume the absolute path at
|
||||
which it is being served, it will just work no matter what port you decide to serve it off
|
||||
or if you put it in behind code-server or any other proxy!
|
||||
|
||||
However many people prefer the cleaner aesthetic of no trailing slashes. This couples you
|
||||
to the base path as you cannot use relative redirects correctly anymore. See the above
|
||||
link.
|
||||
|
||||
For users who are ok with this tradeoff, pass `--proxy-path-passthrough` to code-server
|
||||
and the path will be passed as is.
|
||||
|
||||
This is particularly a problem with the `start` script in create-react-app. See
|
||||
[#2222](https://github.com/cdr/code-server/issues/2222). You will need to inform
|
||||
create-react-app of the path at which you are serving via `homepage` field in your
|
||||
`package.json`. e.g. you'd add the following for the default CRA port:
|
||||
|
||||
```json
|
||||
"homepage": "/proxy/3000",
|
||||
```
|
||||
|
||||
Then visit `https://my-code-server-address.io/proxy/3000` to see your app exposed through
|
||||
code-server!
|
||||
|
||||
Unfortunately `webpack-dev-server`'s websocket connections will not go through as it
|
||||
always uses `/sockjs-node`. So hot reloading will not work until the `create-react-app`
|
||||
team fix this bug.
|
||||
|
||||
Highly recommend using the subdomain approach instead to avoid this class of issue.
|
||||
|
||||
## Multi-tenancy
|
||||
|
||||
If you want to run multiple code-servers on shared infrastructure, we recommend using virtual
|
||||
|
@ -38,13 +38,13 @@
|
||||
"@types/js-yaml": "^3.12.3",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^12.12.7",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/parcel-bundler": "^1.12.1",
|
||||
"@types/pem": "^1.9.5",
|
||||
"@types/proxy-from-env": "^1.0.1",
|
||||
"@types/safe-compare": "^1.1.0",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/split2": "^2.1.6",
|
||||
"@types/supertest": "^2.0.10",
|
||||
"@types/tar-fs": "^2.0.0",
|
||||
"@types/tar-stream": "^2.1.0",
|
||||
"@types/ws": "^7.2.6",
|
||||
@ -61,7 +61,6 @@
|
||||
"prettier": "^2.0.5",
|
||||
"stylelint": "^13.0.0",
|
||||
"stylelint-config-recommended": "^3.0.0",
|
||||
"supertest": "^6.0.1",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "4.0.2"
|
||||
},
|
||||
@ -81,6 +80,7 @@
|
||||
"httpolyglot": "^0.1.2",
|
||||
"js-yaml": "^3.13.1",
|
||||
"limiter": "^1.1.5",
|
||||
"node-fetch": "^2.6.1",
|
||||
"pem": "^1.14.2",
|
||||
"proxy-agent": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
|
@ -112,3 +112,11 @@ export const getFirstString = (value: string | string[] | object | undefined): s
|
||||
|
||||
return typeof value === "string" ? value : undefined
|
||||
}
|
||||
|
||||
export function logError(prefix: string, err: any): void {
|
||||
if (err instanceof Error) {
|
||||
logger.error(`${prefix}: ${err.message} ${err.stack}`)
|
||||
} else {
|
||||
logger.error(`${prefix}: ${err}`)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import express, { Express } from "express"
|
||||
import { promises as fs } from "fs"
|
||||
import http from "http"
|
||||
import * as httpolyglot from "httpolyglot"
|
||||
import * as util from "../common/util"
|
||||
import { DefaultedArgs } from "./cli"
|
||||
import { handleUpgrade } from "./wsRouter"
|
||||
|
||||
@ -22,8 +23,21 @@ export const createApp = async (args: DefaultedArgs): Promise<[Express, Express,
|
||||
)
|
||||
: http.createServer(app)
|
||||
|
||||
await new Promise<http.Server>(async (resolve, reject) => {
|
||||
server.on("error", reject)
|
||||
let resolved = false
|
||||
await new Promise<http.Server>(async (resolve2, reject) => {
|
||||
const resolve = () => {
|
||||
resolved = true
|
||||
resolve2()
|
||||
}
|
||||
server.on("error", (err) => {
|
||||
if (!resolved) {
|
||||
reject(err)
|
||||
} else {
|
||||
// Promise resolved earlier so this is an unrelated error.
|
||||
util.logError("http server error", err)
|
||||
}
|
||||
})
|
||||
|
||||
if (args.socket) {
|
||||
try {
|
||||
await fs.unlink(args.socket)
|
||||
|
@ -50,6 +50,7 @@ export interface Args extends VsArgs {
|
||||
"show-versions"?: boolean
|
||||
"uninstall-extension"?: string[]
|
||||
"proxy-domain"?: string[]
|
||||
"proxy-path-passthrough"?: boolean
|
||||
locale?: string
|
||||
_: string[]
|
||||
"reuse-window"?: boolean
|
||||
@ -172,6 +173,10 @@ const options: Options<Required<Args>> = {
|
||||
"uninstall-extension": { type: "string[]", description: "Uninstall a VS Code extension by id." },
|
||||
"show-versions": { type: "boolean", description: "Show VS Code extension versions." },
|
||||
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
|
||||
"proxy-path-passthrough": {
|
||||
type: "boolean",
|
||||
description: "Whether the path proxy should leave the /proxy/<port> in the request path when proxying.",
|
||||
},
|
||||
"ignore-last-opened": {
|
||||
type: "boolean",
|
||||
short: "e",
|
||||
@ -239,7 +244,7 @@ export const optionDescriptions = (): string[] => {
|
||||
export const parse = (
|
||||
argv: string[],
|
||||
opts?: {
|
||||
configFile: string
|
||||
configFile?: string
|
||||
},
|
||||
): Args => {
|
||||
const error = (msg: string): Error => {
|
||||
@ -516,7 +521,19 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
|
||||
}
|
||||
|
||||
const configFile = await fs.readFile(configPath)
|
||||
const config = yaml.safeLoad(configFile.toString(), {
|
||||
return parseConfigFile(configFile.toString(), configPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* parseConfigFile parses configFile into ConfigArgs.
|
||||
* configPath is used as the filename in error messages
|
||||
*/
|
||||
export function parseConfigFile(configFile: string, configPath: string): ConfigArgs {
|
||||
if (!configFile) {
|
||||
return { _: [], config: configPath }
|
||||
}
|
||||
|
||||
const config = yaml.safeLoad(configFile, {
|
||||
filename: configPath,
|
||||
})
|
||||
if (!config || typeof config === "string") {
|
||||
|
@ -45,4 +45,13 @@ export class Heart {
|
||||
})
|
||||
}, this.heartbeatInterval)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to clear any heartbeatTimer for shutdown.
|
||||
*/
|
||||
public dispose(): void {
|
||||
if (typeof this.heartbeatTimer !== "undefined") {
|
||||
clearTimeout(this.heartbeatTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,9 @@ export const register = async (
|
||||
})
|
||||
})
|
||||
})
|
||||
server.on("close", () => {
|
||||
heart.dispose()
|
||||
})
|
||||
|
||||
app.disable("x-powered-by")
|
||||
wsApp.disable("x-powered-by")
|
||||
@ -165,7 +168,7 @@ export const register = async (
|
||||
|
||||
app.use(errorHandler)
|
||||
|
||||
const wsErrorHandler: express.ErrorRequestHandler = async (err, req) => {
|
||||
const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
|
||||
logger.error(`${err.message} ${err.stack}`)
|
||||
;(req as WebsocketRequest).ws.end()
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ import { Router as WsRouter } from "../wsRouter"
|
||||
|
||||
export const router = Router()
|
||||
|
||||
const getProxyTarget = (req: Request, rewrite: boolean): string => {
|
||||
if (rewrite) {
|
||||
const getProxyTarget = (req: Request, passthroughPath: boolean): string => {
|
||||
if (passthroughPath) {
|
||||
return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
|
||||
}
|
||||
const query = qs.stringify(req.query)
|
||||
return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
|
||||
}
|
||||
return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
|
||||
}
|
||||
|
||||
router.all("/(:port)(/*)?", (req, res) => {
|
||||
@ -33,7 +33,7 @@ router.all("/(:port)(/*)?", (req, res) => {
|
||||
|
||||
proxy.web(req, res, {
|
||||
ignorePath: true,
|
||||
target: getProxyTarget(req, true),
|
||||
target: getProxyTarget(req, req.args["proxy-path-passthrough"] || false),
|
||||
})
|
||||
})
|
||||
|
||||
@ -42,6 +42,6 @@ export const wsRouter = WsRouter()
|
||||
wsRouter.ws("/(:port)(/*)?", ensureAuthenticated, (req) => {
|
||||
proxy.ws(req, req.ws, req.head, {
|
||||
ignorePath: true,
|
||||
target: getProxyTarget(req, true),
|
||||
target: getProxyTarget(req, req.args["proxy-path-passthrough"] || false),
|
||||
})
|
||||
})
|
||||
|
72
test/httpserver.ts
Normal file
72
test/httpserver.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import * as http from "http"
|
||||
import * as nodeFetch from "node-fetch"
|
||||
import * as util from "../src/common/util"
|
||||
import { ensureAddress } from "../src/node/app"
|
||||
|
||||
// Perhaps an abstraction similar to this should be used in app.ts as well.
|
||||
export class HttpServer {
|
||||
private hs = http.createServer()
|
||||
|
||||
public constructor(hs?: http.Server) {
|
||||
// See usage in test/integration.ts
|
||||
if (hs) {
|
||||
this.hs = hs
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* listen starts the server on a random localhost port.
|
||||
* Use close to cleanup when done.
|
||||
*/
|
||||
public listen(fn: http.RequestListener): Promise<void> {
|
||||
this.hs.on("request", fn)
|
||||
|
||||
let resolved = false
|
||||
return new Promise((res, rej) => {
|
||||
this.hs.listen(0, "localhost", () => {
|
||||
res()
|
||||
resolved = true
|
||||
})
|
||||
|
||||
this.hs.on("error", (err) => {
|
||||
if (!resolved) {
|
||||
rej(err)
|
||||
} else {
|
||||
// Promise resolved earlier so this is some other error.
|
||||
util.logError("http server error", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* close cleans up the server.
|
||||
*/
|
||||
public close(): Promise<void> {
|
||||
return new Promise((res, rej) => {
|
||||
this.hs.close((err) => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
return
|
||||
}
|
||||
res()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch fetches the request path.
|
||||
* The request path must be rooted!
|
||||
*/
|
||||
public fetch(requestPath: string, opts?: nodeFetch.RequestInit): Promise<nodeFetch.Response> {
|
||||
return nodeFetch.default(`${ensureAddress(this.hs)}${requestPath}`, opts)
|
||||
}
|
||||
|
||||
public port(): number {
|
||||
const addr = this.hs.address()
|
||||
if (addr && typeof addr === "object") {
|
||||
return addr.port
|
||||
}
|
||||
throw new Error("server not listening or listening on unix socket")
|
||||
}
|
||||
}
|
21
test/integration.ts
Normal file
21
test/integration.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import * as express from "express"
|
||||
import { createApp } from "../src/node/app"
|
||||
import { parse, setDefaults, parseConfigFile, DefaultedArgs } from "../src/node/cli"
|
||||
import { register } from "../src/node/routes"
|
||||
import * as httpserver from "./httpserver"
|
||||
|
||||
export async function setup(
|
||||
argv: string[],
|
||||
configFile?: string,
|
||||
): Promise<[express.Application, express.Application, httpserver.HttpServer, DefaultedArgs]> {
|
||||
argv = ["--bind-addr=localhost:0", ...argv]
|
||||
|
||||
const cliArgs = parse(argv)
|
||||
const configArgs = parseConfigFile(configFile || "", "test/integration.ts")
|
||||
const args = await setDefaults(cliArgs, configArgs)
|
||||
|
||||
const [app, wsApp, server] = await createApp(args)
|
||||
await register(app, wsApp, server, args)
|
||||
|
||||
return [app, wsApp, new httpserver.HttpServer(server), args]
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import { logger } from "@coder/logger"
|
||||
import * as assert from "assert"
|
||||
import * as express from "express"
|
||||
import * as fs from "fs"
|
||||
import { describe } from "mocha"
|
||||
import * as path from "path"
|
||||
import * as supertest from "supertest"
|
||||
import { PluginAPI } from "../src/node/plugin"
|
||||
import * as apps from "../src/node/routes/apps"
|
||||
import * as httpserver from "./httpserver"
|
||||
const fsp = fs.promises
|
||||
|
||||
/**
|
||||
@ -13,23 +14,30 @@ const fsp = fs.promises
|
||||
*/
|
||||
describe("plugin", () => {
|
||||
let papi: PluginAPI
|
||||
let app: express.Application
|
||||
let agent: supertest.SuperAgentTest
|
||||
let s: httpserver.HttpServer
|
||||
|
||||
before(async () => {
|
||||
papi = new PluginAPI(logger, path.resolve(__dirname, "test-plugin") + ":meow")
|
||||
papi = new PluginAPI(logger, `${path.resolve(__dirname, "test-plugin")}:meow`)
|
||||
await papi.loadPlugins()
|
||||
|
||||
app = express.default()
|
||||
const app = express.default()
|
||||
papi.mount(app)
|
||||
|
||||
app.use("/api/applications", apps.router(papi))
|
||||
|
||||
agent = supertest.agent(app)
|
||||
s = new httpserver.HttpServer()
|
||||
await s.listen(app)
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await s.close()
|
||||
})
|
||||
|
||||
it("/api/applications", async () => {
|
||||
await agent.get("/api/applications").expect(200, [
|
||||
const resp = await s.fetch("/api/applications")
|
||||
assert.equal(200, resp.status)
|
||||
const body = await resp.json()
|
||||
logger.debug(`${JSON.stringify(body)}`)
|
||||
assert.deepEqual(body, [
|
||||
{
|
||||
name: "Test App",
|
||||
version: "4.0.0",
|
||||
@ -57,6 +65,9 @@ describe("plugin", () => {
|
||||
const indexHTML = await fsp.readFile(path.join(__dirname, "test-plugin/public/index.html"), {
|
||||
encoding: "utf8",
|
||||
})
|
||||
await agent.get("/test-plugin/test-app").expect(200, indexHTML)
|
||||
const resp = await s.fetch("/test-plugin/test-app")
|
||||
assert.equal(200, resp.status)
|
||||
const body = await resp.text()
|
||||
assert.equal(body, indexHTML)
|
||||
})
|
||||
})
|
||||
|
47
test/proxy.test.ts
Normal file
47
test/proxy.test.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import * as assert from "assert"
|
||||
import * as express from "express"
|
||||
import * as httpserver from "./httpserver"
|
||||
import * as integration from "./integration"
|
||||
|
||||
describe("proxy", () => {
|
||||
let codeServer: httpserver.HttpServer | undefined
|
||||
const nhooyrDevServer = new httpserver.HttpServer()
|
||||
let proxyPath: string
|
||||
|
||||
before(async () => {
|
||||
const e = express.default()
|
||||
await nhooyrDevServer.listen(e)
|
||||
e.get("/wsup", (req, res) => {
|
||||
res.json("asher is the best")
|
||||
})
|
||||
proxyPath = `/proxy/${nhooyrDevServer.port()}/wsup`
|
||||
e.get(proxyPath, (req, res) => {
|
||||
res.json("joe is the best")
|
||||
})
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await nhooyrDevServer.close()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (codeServer) {
|
||||
await codeServer.close()
|
||||
codeServer = undefined
|
||||
}
|
||||
})
|
||||
|
||||
it("should rewrite the base path", async () => {
|
||||
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||
const resp = await codeServer.fetch(proxyPath)
|
||||
assert.equal(resp.status, 200)
|
||||
assert.equal(await resp.json(), "asher is the best")
|
||||
})
|
||||
|
||||
it("should not rewrite the base path", async () => {
|
||||
;[, , codeServer] = await integration.setup(["--auth=none", "--proxy-path-passthrough=true"], "")
|
||||
const resp = await codeServer.fetch(proxyPath)
|
||||
assert.equal(resp.status, 200)
|
||||
assert.equal(await resp.json(), "joe is the best")
|
||||
})
|
||||
})
|
87
yarn.lock
87
yarn.lock
@ -1040,11 +1040,6 @@
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/cookiejar@*":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8"
|
||||
integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==
|
||||
|
||||
"@types/express-serve-static-core@*":
|
||||
version "4.17.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz#d9af025e925fc8b089be37423b8d1eac781be084"
|
||||
@ -1108,6 +1103,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.3.tgz#51b21b6acb6d1b923bbdc7725c38f9f455166402"
|
||||
integrity sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==
|
||||
|
||||
"@types/node-fetch@^2.5.7":
|
||||
version "2.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
|
||||
integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*", "@types/node@^12.12.7":
|
||||
version "12.12.67"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.67.tgz#4f86badb292e822e3b13730a1f9713ed2377f789"
|
||||
@ -1184,21 +1187,6 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/superagent@*":
|
||||
version "4.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.10.tgz#5e2cc721edf58f64fe9b819f326ee74803adee86"
|
||||
integrity sha512-xAgkb2CMWUMCyVc/3+7iQfOEBE75NvuZeezvmixbUw3nmENf2tCnQkW5yQLTYqvXUQ+R6EXxdqKKbal2zM5V/g==
|
||||
dependencies:
|
||||
"@types/cookiejar" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/supertest@^2.0.10":
|
||||
version "2.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.10.tgz#630d79b4d82c73e043e43ff777a9ca98d457cab7"
|
||||
integrity sha512-Xt8TbEyZTnD5Xulw95GLMOkmjGICrOQyJ2jqgkSjAUR3mm7pAIzSR0NFBaMcwlzVvlpCjNwbATcWWwjNiZiFrQ==
|
||||
dependencies:
|
||||
"@types/superagent" "*"
|
||||
|
||||
"@types/tar-fs@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-2.0.0.tgz#db94cb4ea1cccecafe3d1a53812807efb4bbdbc1"
|
||||
@ -2255,7 +2243,7 @@ commander@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
|
||||
component-emitter@^1.2.1, component-emitter@^1.3.0:
|
||||
component-emitter@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
@ -2327,11 +2315,6 @@ cookie@0.4.0:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
|
||||
cookiejar@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
|
||||
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
|
||||
|
||||
copy-descriptor@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
@ -3428,11 +3411,6 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fast-safe-stringify@^2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
|
||||
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
|
||||
|
||||
fastest-levenshtein@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
|
||||
@ -3603,11 +3581,6 @@ format@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
|
||||
integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
|
||||
|
||||
formidable@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
|
||||
integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
@ -4978,7 +4951,7 @@ merge2@^1.2.3, merge2@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
methods@1.1.2, methods@^1.1.2, methods@~1.1.2:
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||
@ -5035,11 +5008,6 @@ mime@1.6.0:
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.4.6:
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
||||
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
||||
|
||||
mimic-fn@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
||||
@ -5214,6 +5182,11 @@ node-addon-api@^1.7.1:
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
|
||||
integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-forge@^0.7.1:
|
||||
version "0.7.6"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
|
||||
@ -6405,11 +6378,6 @@ qs@6.7.0:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
qs@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687"
|
||||
integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
@ -7522,31 +7490,6 @@ sugarss@^2.0.0:
|
||||
dependencies:
|
||||
postcss "^7.0.2"
|
||||
|
||||
superagent@6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/superagent/-/superagent-6.1.0.tgz#09f08807bc41108ef164cfb4be293cebd480f4a6"
|
||||
integrity sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==
|
||||
dependencies:
|
||||
component-emitter "^1.3.0"
|
||||
cookiejar "^2.1.2"
|
||||
debug "^4.1.1"
|
||||
fast-safe-stringify "^2.0.7"
|
||||
form-data "^3.0.0"
|
||||
formidable "^1.2.2"
|
||||
methods "^1.1.2"
|
||||
mime "^2.4.6"
|
||||
qs "^6.9.4"
|
||||
readable-stream "^3.6.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
supertest@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.0.1.tgz#f6b54370de85c45d6557192c8d7df604ca2c9e18"
|
||||
integrity sha512-8yDNdm+bbAN/jeDdXsRipbq9qMpVF7wRsbwLgsANHqdjPsCoecmlTuqEcLQMGpmojFBhxayZ0ckXmLXYq7e+0g==
|
||||
dependencies:
|
||||
methods "1.1.2"
|
||||
superagent "6.1.0"
|
||||
|
||||
supports-color@7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
|
||||
|
Reference in New Issue
Block a user