diff --git a/LICENSE b/LICENSE index fb674fe..ee7c1ab 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Gary Meehan +Copyright (c) 2018 Sammy Teillet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5c0e6bc..3f850a6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Local CORS Proxy +# Local CORS Proxy + Webhook Store Simple proxy to bypass CORS issues. This was built as a local dev only solution to enable prototyping against existing APIs without having to worry about CORS. @@ -11,45 +11,47 @@ No 'Access-Control-Allow-Origin' header is present on the requested resource. Or ## Getting Started ``` -npm install -g local-cors-proxy +npx webhook-store-cli@latest ``` **Simple Example** -API endpoint that we want to request that has CORS issues: +Start Proxy and open Webhook Store client in a tab: ``` -https://www.yourdomain.ie/movies/list +npx webhook-store-cli@latest ``` -Start Proxy: +Start Proxy and open a specific Webhook Store client: ``` -lcp --proxyUrl https://www.yourdomain.ie +npx webhook-store-cli@latest --webhookStore https://lol.webhook.store/ ``` -Then in your client code, new API endpoint: +Start Proxy without opening a tab: ``` -http://localhost:8010/proxy/movies/list +npx webhook-store-cli@latest --noOpen ``` -End result will be a request to `https://www.yourdomain.ie/movies/list` without the CORS issues! +Start Proxy to target port 9000: -Alternatively you can install the package locally and add a script to your project: +``` +npx webhook-store-cli@latest --port 9000 +``` -```json - "scripts": { - "proxy": "lcp --proxyUrl https://www.yourdomain.ie" - } +Start Proxy to target specific port, hostname and protocol: + +``` +npx webhook-store-cli@latest --protocol https --hostname dev.localenv --port 9000 ``` ## Options -| Option | Example | Default | -| -------------- | --------------------- | ------: | -| --proxyUrl | https://www.google.ie | | -| --proxyPartial | foo | proxy | -| --port | 8010 | 8010 | -| --credentials | (no value needed) | false | -| --origin | http://localhost:4200 | * | +| Option | Example | Default | +| -------------- | ------------------------- | --------: | +| --webhookStore | https://lol.webhook.store | | +| --noOpen | (no value needed) | | +| --port | 9000 | 9000 | +| --protocol | https | http | +| --hostname | dev.localenv | localhost | diff --git a/bin/lcp.js b/bin/lcp.js index bb4b137..393dc32 100644 --- a/bin/lcp.js +++ b/bin/lcp.js @@ -1,26 +1,91 @@ #!/usr/bin/env node -var lcp = require('../lib/index.js'); -var commandLineArgs = require('command-line-args'); +var lcp = require("../lib/index.js"); +var commandLineArgs = require("command-line-args"); +var chalk = require("chalk"); +const prompt = require("prompt-sync")(); var optionDefinitions = [ - { name: 'port', alias: 'p', type: Number, defaultValue: 8010 }, + { name: "proxyPort", alias: "p", type: Number, defaultValue: 8010 }, + { name: "protocol", type: String, defaultValue: "http" }, + { name: "hostname", type: String, defaultValue: "localhost" }, + { name: "port", type: String }, + { name: "credentials", type: Boolean, defaultValue: false }, + { name: "origin", type: String, defaultValue: "*" }, + { name: "webhookStore", type: String }, + { name: "noOpen", type: Boolean, default: false }, { - name: 'proxyPartial', - type: String, - defaultValue: '/proxy' + name: "help", + type: Boolean, + alias: "h", + description: "print out helpful usage information", }, - { name: 'proxyUrl', type: String }, - { name: 'credentials', type: Boolean, defaultValue: false }, - { name: 'origin', type: String, defaultValue: '*' } ]; +function getDefaultSubdomain() { + const defaultSubdomain = "hackator10"; + try { + const username = require("os").userInfo().username; + return username.replace(/[^a-zA-Z0-9]/g, "") || defaultSubdomain; + } catch { + return defaultSubdomain; + } +} + try { var options = commandLineArgs(optionDefinitions); - if (!options.proxyUrl) { - throw new Error('--proxyUrl is required'); + if (options.help) { + console.warn(chalk.yellow.bold("Options")); + console.warn( + chalk.yellow( + "webhookStore: " + + chalk.green( + "Url of you webhook store. Example: --webhookStore https://coucou.webhook.store" + ) + ) + ); + console.warn( + chalk.yellow( + "port: " + + chalk.green( + "The port you want forward the webhooks. Example: --port 9001" + ) + ) + ); + } else { + if (!options.webhookStore && !options.noOpen) { + console.log(chalk.yellow("--webhookStore was not provided.")); + const defaultSubdomain = getDefaultSubdomain(); + const subdomainInput = + prompt( + `Claim your subdomain (your name or company name. e.g. ${defaultSubdomain}): ` + ) || defaultSubdomain; + const subdomain = + subdomainInput.replace(/[^a-zA-Z0-9]/g, "") || defaultSubdomain; + options.webhookStore = `https://${subdomain}.webhook.store`; + console.log( + chalk.yellow( + `Next time run with '--webhookStore ${options.webhookStore}'` + ) + ); + } + if (!options.port) { + console.log(chalk.yellow("--port option was not provided")); + const portInput = + prompt( + `Which port would you like to redirect webhooks on (default :9000): ` + ) || 9000; + options.port = isNaN(portInput) ? 9000 : portInput; + } + lcp.startProxy( + options.proxyPort, + options.protocol + "://" + options.hostname + ":" + options.port, + options.credentials, + options.origin, + options.webhookStore, + options.noOpen + ); } - lcp.startProxy(options.port, options.proxyUrl, options.proxyPartial, options.credentials, options.origin); } catch (error) { - console.error(error); + console.error(chalk.red(error)); } diff --git a/lib/index.js b/lib/index.js index 6dcbec6..fde6688 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,50 +1,81 @@ -var express = require('express'); -var request = require('request'); -var cors = require('cors'); -var chalk = require('chalk'); +var express = require("express"); +var request = require("request"); +var cors = require("cors"); +var chalk = require("chalk"); +var open = require("open"); var proxy = express(); -var startProxy = function(port, proxyUrl, proxyPartial, credentials, origin) { - proxy.use(cors({credentials: credentials, origin: origin})); - proxy.options('*', cors({credentials: credentials, origin: origin})); +var startProxy = function ( + port, + proxyUrl, + credentials, + origin, + webhookStoreUrl, + shouldNotOpen +) { + proxy.use(cors({ credentials: credentials, origin: origin })); + proxy.options("*", cors({ credentials: credentials, origin: origin })); // remove trailing slash - var cleanProxyUrl = proxyUrl.replace(/\/$/, ''); - // remove all forward slashes - var cleanProxyPartial = proxyPartial.replace(/\//g, ''); + var cleanProxyUrl = proxyUrl.replace(/\/$/, ""); + var cleanProxyPartial = "proxy"; - proxy.use('/' + cleanProxyPartial, function(req, res) { + proxy.use("/" + cleanProxyPartial, function (req, res, next) { try { - console.log(chalk.green('Request Proxied -> ' + req.url)); + console.log(chalk.green("Request Proxied -> " + req.url)); + + var originalHeaders = JSON.parse(req.headers["x-ws-original-headers"]); + Object.keys(originalHeaders).forEach(function (key) { + req.headers[key] = originalHeaders[key]; + }); } catch (e) {} - req.pipe( - request(cleanProxyUrl + req.url) - .on('response', response => { - // In order to avoid https://github.com/expressjs/cors/issues/134 - const accessControlAllowOriginHeader = response.headers['access-control-allow-origin'] - if(accessControlAllowOriginHeader && accessControlAllowOriginHeader !== origin ){ - console.log(chalk.blue('Override access-control-allow-origin header from proxified URL : ' + chalk.green(accessControlAllowOriginHeader) + '\n')); - response.headers['access-control-allow-origin'] = origin; - } - }) - ).pipe(res); + req + .pipe( + request(cleanProxyUrl + req.url) + .on("response", (response) => { + console.log(chalk.blue("Response code: " + response.statusCode)); + // In order to avoid https://github.com/expressjs/cors/issues/134 + const accessControlAllowOriginHeader = + response.headers["access-control-allow-origin"]; + if ( + accessControlAllowOriginHeader && + accessControlAllowOriginHeader !== origin + ) { + console.log( + chalk.blue( + "Override access-control-allow-origin header from proxified URL : " + + chalk.green(accessControlAllowOriginHeader) + + "\n" + ) + ); + response.headers["access-control-allow-origin"] = origin; + } + }) + .on("error", next) + ) + .pipe(res); + }); + + proxy.use("/health", (_req, res) => { + res.send("ok"); }); proxy.listen(port); // Welcome Message - console.log(chalk.bgGreen.black.bold.underline('\n Proxy Active \n')); - console.log(chalk.blue('Proxy Url: ' + chalk.green(cleanProxyUrl))); - console.log(chalk.blue('Proxy Partial: ' + chalk.green(cleanProxyPartial))); - console.log(chalk.blue('PORT: ' + chalk.green(port))); - console.log(chalk.blue('Credentials: ' + chalk.green(credentials))); - console.log(chalk.blue('Origin: ' + chalk.green(origin) + '\n')); - console.log( - chalk.cyan( - 'To start using the proxy simply replace the proxied part of your url with: ' + - chalk.bold('http://localhost:' + port + '/' + cleanProxyPartial + '\n') - ) - ); + console.log(chalk.bgGreen.black.bold.underline("\n Proxy Active \n")); + console.log(chalk.blue("Proxy Url: " + chalk.green(cleanProxyUrl))); + const shouldOpen = !shouldNotOpen; + if (shouldOpen) { + console.log( + chalk.blue("Webhook Store: " + chalk.green(webhookStoreUrl) + "\n") + ); + + console.log( + chalk.cyan("Now opening the webhook store " + chalk.bold(webhookStoreUrl)) + ); + setTimeout(open, 1000, webhookStoreUrl); + } }; exports.startProxy = startProxy; diff --git a/package.json b/package.json index 4f4ac66..7434e0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "local-cors-proxy", - "version": "1.1.0", + "name": "webhook-store-cli", + "version": "1.1.5", "main": "./lib/index.js", "scripts": { "start": "node ./bin/lcp.js", @@ -9,28 +9,29 @@ "bin": { "lcp": "./bin/lcp.js" }, - "author": "Gary Meehan", + "author": "Sammy Teillet", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/garmeeh/local-cors-proxy.git" + "url": "git+https://github.com/OpenWebhook/webhook-store-cli.git" }, "keywords": [ - "cors", - "proxy", "simple", - "node", - "express" + "webhook", + "webhook-store", + "dev-tool" ], "bugs": { - "url": "https://github.com/garmeeh/local-cors-proxy/issues" + "url": "https://github.com/OpenWebhook/webhook-store-cli/issues" }, - "homepage": "https://github.com/garmeeh/local-cors-proxy#readme", + "homepage": "https://github.com/OpenWebhook/webhook-store-cli#readme", "dependencies": { "chalk": "^2.3.2", "command-line-args": "^5.0.2", "cors": "^2.8.4", "express": "^4.16.3", + "open": "^8.4.0", + "prompt-sync": "^4.2.0", "request": "^2.85.0" } } diff --git a/yarn.lock b/yarn.lock index 6d3f2c3..ff6e113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,11 @@ ajv@^5.1.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -185,6 +190,11 @@ debug@2.6.9: dependencies: ms "2.0.0" +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -384,10 +394,22 @@ ipaddr.js@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -469,6 +491,15 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -481,6 +512,13 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" +prompt-sync@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/prompt-sync/-/prompt-sync-4.2.0.tgz#0198f73c5b70e3b03e4b9033a50540a7c9a1d7f4" + integrity sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw== + dependencies: + strip-ansi "^5.0.0" + proxy-addr@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" @@ -603,6 +641,13 @@ stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" +strip-ansi@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + supports-color@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0"