├── .gitignore ├── LICENSE.md ├── README.md ├── bin └── userscript-proxy.js ├── config └── index.html ├── examples ├── config.json ├── scripts │ ├── background.js │ └── google.js └── styles │ ├── background.css │ └── google.css ├── lib └── userscript-proxy.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .http-mitm-proxy 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | SPDX short identifier: MIT 3 | 4 | Copyright 2018 eBay Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Userscript Proxy 2 | 3 | [![Join the chat at https://gitter.im/eBay/userscript-proxy](https://badges.gitter.im/eBay/userscript-proxy.svg)](https://gitter.im/eBay/userscript-proxy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Ever tried to run userscripts on your iOS device or your Android phone? For iOS, it's not possible, and for Android it's an exercise in frustration. 6 | 7 | Userscript Proxy allows you to run userscripts on mobile devices. Userscripts are snippets of JavaScript and CSS code that are added to a particular web page, to change the way it looks or the way it behaves. [Tampermonkey](https://tampermonkey.net) and [Greasemonkey](http://www.greasespot.net) are browser extensions that allow you to install userscripts into web pages, but they only work properly in desktop browsers – they don't support iOS at all, and the version for Android is tied to an old browser that doesn't work very well anymore. 8 | 9 | Instead of running userscripts directly in the browser, Userscript Proxy is an HTTP proxy server that transparently injects scripts and stylesheets (userscripts) into existing sites. We use this tool to quickly generate research stimuli for user testing: we write JavaScript scripts and CSS stylesheets that alter our existing sites according to new designs. This makes it much easier for our developers when we want to test out a simple change: we can run our changes on top of the existing site. 10 | 11 | With Userscript Proxy, you can set up a collection of scripts, run multiple scripts at once, and specify which hosts and URLs to inject each script into. You can think of it as Tampermonkey or Greasemonkey running inside of a proxy server. 12 | 13 | ## How it works 14 | 15 | Userscript Proxy generates a proxy PAC file that specifies which web hosts have URLs that need to be altered, and directs all requests to those hosts to Userscript Proxy's proxy server. The proxy server then adds userscripts to the HTML file that is served from each URL that matches. Scripts and stylesheets are added inline to the proxied page, so they run with the same permissions as a script running directly on the website. 16 | 17 | Userscript Proxy uses the [http-mitm-proxy](https://github.com/joeferner/node-http-mitm-proxy/) npm module for the core proxy functionality. 18 | 19 | ## Installing 20 | 21 | Userscript Proxy is a [Node.js](https://nodejs.org) application. The proxy can be used as a command-line application or as a Node module. Install as a command-line application unless you want to include the proxy in software you're writing. 22 | 23 | ### Installing as stand-alone command-line application 24 | 25 | * Download and install [Node.js](https://nodejs.org) 26 | * Open a Terminal window and install Userscript Proxy: 27 | 28 | ```sh 29 | npm install -g userscript-proxy 30 | ``` 31 | 32 | #### Running with example files 33 | 34 | To run Userscript Proxy, you need a configuration file (config.json) and userscripts, which are JavaScript and CSS files. You can get a feel for how it works with the example files on this page. Clone this repository to your computer or [download the ZIP file](https://github.corp.ebay.com/EPIC/userscript-proxy/archive/master.zip) from the GitHub page, open a Terminal window and navigate to the `examples` directory, then run: 35 | 36 | ```sh 37 | userscript-proxy config.json 38 | ``` 39 | 40 | See "Using the proxy server" below to set it up on your devices. 41 | 42 | #### Running on the command line 43 | 44 | In general, run Userscript Proxy as follows: 45 | 46 | ```sh 47 | userscript-proxy [options] 48 | ``` 49 | 50 | * `` is the path to a JSON file that specifies the configuration of the proxy (see below for config file format) 51 | * `[options]` include: 52 | 53 | ```sh 54 | --loadScript set Start the proxy with the given set of userscripts active (defined in the config file) 55 | --staticDir dir Path to directory of static files to serve at /static 56 | --proxyPort port Port for HTTP proxy server (default 8888) 57 | --pacPort port Port to serve PAC and other static files (default 8080) 58 | --limitHosts Only proxy hosts in the currently-selected userscript set 59 | (see "Notes" section for more details) 60 | --addPac file Path to existing PAC file to add proxy rules to 61 | (see "Notes" section for more details) 62 | --proxyIndex file Path to proxy index HTML file 63 | --auth Enable proxy authentication (experimental; see "Notes" section) 64 | ``` 65 | 66 | ### Installing as an npm module 67 | 68 | ```sh 69 | npm install --save userscript-proxy 70 | ``` 71 | 72 | Add the following code to your script: 73 | 74 | ```javascript 75 | var proxy = require('userscript-proxy'); 76 | proxy(config, id, options); 77 | ``` 78 | 79 | * `config` is a JavaScript object that specifies the configuration of the proxy (see below for config format) 80 | * `id` is the id of the set of userscripts you want to enable 81 | * `options` is an optional parameter with proxy options: 82 | 83 | ```javascript 84 | { 85 | 'loadScript': 'userscriptSetId', // Start the proxy with the given set of userscripts active (defined in config) 86 | 'staticDir': './path/to/static', // Path to directory of static files to serve at /static 87 | 'proxyPort': 8888, // Port for HTTP proxy server (default 8888) 88 | 'pacPort': 8080, // Port to serve PAC and other static files (default 8080) 89 | 'limitHosts': false, // Only proxy hosts in the currently-selected userscript set 90 | // (see "Notes" section for more details) 91 | 'addPac': pacFileString, // Add proxy rules to the beginning of an existing PAC file (as string) 92 | // (see "Notes" section for more details) 93 | 'proxyIndex': indexFileString, // Proxy index HTML file (as string) 94 | 'auth': false // Enable proxy authentication (experimental; see "Notes" section) 95 | } 96 | ``` 97 | 98 | ### Using the proxy server 99 | 100 | * Connect the device that you wish to run userscripts on to the same network as your server. 101 | * Load `http://:8080/` in the browser of the device that will run the userscripts, where `` is the hostname or IP of the proxy server. 102 | * Install the CA certificate on the device that will run the userscripts. Download the CA Certificate from the page you just loaded. There are detailed instructions on the proxy's web page for multiple platforms. 103 | * Set the Automatic HTTP Proxy URL on the device that will run the userscripts. Copy the link from the page you just loaded to your device's proxy settings. There are detailed instructions on the proxy's web page for multiple platforms. 104 | * If you select a different set of userscripts from `http://:8080/`, the proxy will globally and immediately update which sites will have userscripts added to them. 105 | 106 | ## Creating userscripts 107 | 108 | Userscript Proxy doesn't have a graphical interface like Tampermonkey to install scripts, but don't get scared off – you just need to add the scripts to an easy configuration file. 109 | 110 | See the `examples` directory for a sample `config.json` file and sample scripts. You can run the samples directly by opening a Terminal window in the `examples` directory and running `userscript-proxy config.json` 111 | 112 | Userscripts are defined in a JavaScript object, defined below. 113 | 114 | ```json 115 | { 116 | "userscripts": [ 117 | { 118 | "title": "Title of userscript", 119 | "id": "userscriptId", 120 | "match": [ 121 | {"host": "example.com", "url": "/"}, 122 | {"host": "example.com", "url": "/index.html"} 123 | ], 124 | "scripts": [ 125 | "./scripts/userscript.js" 126 | ], 127 | "styles": [ 128 | "./styles/userscript.css" 129 | ] 130 | }, 131 | ... 132 | ], 133 | "userscriptSets": [ 134 | { 135 | "title": "Title of userscript set", 136 | "id": "userscriptSetId", 137 | "password": "pass", 138 | "userscripts": ["userscriptId", ...] 139 | }, 140 | ... 141 | ] 142 | } 143 | ``` 144 | 145 | * `userscripts` inject JavaScript or CSS file(s) into specified URL(s). 146 | * `userscriptSets` are collections of individual userscripts that will be enabled at the same time. These sets are what are shown to the user on the proxy web page, and the user can switch between them. 147 | * Individual userscripts have a `title` and `id`, can `match` one or more URLs, can have one or more JavaScript files (`scripts`) injected into that URL, and can have one or more CSS files (`styles`) injected into that URL. 148 | * Sets of userscripts have a `title` and `id`, a `password` that is used when proxy authentication is enabled, and an array of `userscripts` IDs that will all be enabled when this set is selected. 149 | * Every time you add a new userscript to the `userscripts` array, an entry also needs to be added to the `userscriptSets` array to allow the user to enable a userscript or set of userscripts by ID. 150 | * The configuration object is passed directly to the proxy when using it a module, or as a JSON file if the proxy is invoked from the command line. 151 | 152 | ### Adding proxy information to your userscripts 153 | 154 | All instances of the following static keys will be replaced with their corresponding dynamic values in userscripts. 155 | 156 | * `@@USERSCRIPT-PROXY-HOSTNAME-FQDN@@` fully qualified domain name of the proxy server 157 | * `@@USERSCRIPT-PROXY-HOSTNAME@@` hostname of the proxy server 158 | * `@@USERSCRIPT-PROXY-PAC-PORT@@` port number that static files and the PAC are served from 159 | * `@@USERSCRIPT-PROXY-PROXY-PORT@@` port number of the HTTP proxy 160 | * `@@USERSCRIPT-PROXY-STATIC-SERVER@@` base URL of the static directory served by the proxy 161 | * `@@USERSCRIPT-PROXY-LIST@@` HTML list of available userscript sets 162 | * `@@USERSCRIPT-PROXY-PAC-URL@@` URL to download PAC from 163 | * `@@USERSCRIPT-PROXY-CA-URL@@` URL to download CA certificate from 164 | 165 | ## Usage Notes 166 | 167 | * Please note that there currently are no access controls on this proxy server. If you route the port that Userscript Proxy is using directly to the Internet, you will be running an [open proxy](https://en.wikipedia.org/wiki/Open_proxy). 168 | * You can load userscript set-specific PAC files that limit the client device to use the proxy only for the hosts in the specified set of userscripts. Use the following URL in the proxy settings of the client device: `http://:8080/userscript-proxy-.pac`, where `` is the hostname of the proxy server, and `` is the ID of the set of userscripts you want to enable. 169 | * If you want to limit the proxy to only proxy hosts in the currently-selected userscript set (best used in conjunction with userscript set-specific PAC files, as described in the previous bullet), start the proxy server with the option `--limitHosts`. 170 | * If you want to fall back to another set of proxy PAC rules, start the proxy server with the option `--addPac `, where `` is the path to a PAC file on your computer. Userscript Proxy will add its proxy rules to the beginning of the `FindProxyForURL` function in that PAC file. Use the following URL in the proxy settings of the client device: `http://:8080/userscript-proxy--internal.pac`, where `` is the hostname of the proxy server, and `` is the ID of the set of userscripts you want to enable or `all` if you want to allow all hosts in your config to be proxied. 171 | * Userscripts installed with this proxy have the same permissions as scripts running on the page itself, as they are injected directly into the HTML code. Since they are just local scripts to the page, this means that they do not have any of the additional permissions offered by Tampermonkey or Greasemonkey such as cross-site XMLHttpRequests. 172 | * If you've stopped Userscript Proxy, attempted to use it, and then restarted the proxy, your computer may still think that the proxy is down and not try to use it; you may need to force the computer to start using the proxy again by removing it, applying your changes, then enabling it and applying your changes. 173 | 174 | ## Implementation Notes 175 | 176 | * Safari/macOS doesn't pass the URL path from requests to the proxy PAC file, and the PAC file has no visibility into the path when proxying HTTPS sites on any platform. Therefore, we can't filter which sites to proxy based on anything other than the host. 177 | * iOS doesn't let you clear the username/password cache for proxy authentication without a factory reset of the device, so it's not feasible to use proxy authentication to choose which set of userscripts to enable. If you want to try it out anyway, start the proxy with the option `--auth`. 178 | * If you don't install the CA certificate, you will have no issues with unencrypted web pages, but you will get a security warning that you can bypass for regular HTTPS connections and won't be allowed to connect at all for HTTPS connections with HSTS (e.g. Google). 179 | * The CA certificate is automatically generated after running the proxy for the first time. It is located at `~/.userscript-proxy/certs/ca.pem`. If you want to use the generated CA certificate directly from `~/.userscript-proxy/certs/ca.pem`, you may need to convert it to `der` format or use the `crt` file extension, depending on the client device. The proxy serves a version in `der` format with the `crt` extension. 180 | 181 | ## Acknowledgements 182 | 183 | * [Aaron Kleinsteiber](https://github.com/akleinsteiber) (special thanks to Aaron for the project) 184 | * [Anthony Topper](https://github.com/tonytopper) 185 | * [Sean Gates](https://github.com/seangates) 186 | 187 | ## License 188 | 189 | Copyright (c) 2018 eBay Inc. 190 | 191 | Use of this source code is governed by a MIT-style license that can be found in the LICENSE file or at [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT). 192 | -------------------------------------------------------------------------------- /bin/userscript-proxy.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 'use strict'; 3 | 4 | var proxy = require('../lib/userscript-proxy'); 5 | var fs = require('fs'); 6 | var argv = require('minimist')(process.argv.slice(2)); 7 | 8 | //Output usage info if incorrect number of command-line arguments 9 | if (argv['_'].length != 1) { 10 | console.log(`Userscript Proxy: HTTP proxy to inject scripts & stylesheets into existing sites 11 | Usage: node userscript-proxy.js config_file [options] 12 | Options: 13 | --loadScript set Start the proxy with the given set of userscripts active 14 | --staticDir dir Path to directory of static files to serve at /static 15 | --proxyPort port Port for HTTP proxy server (default 8888) 16 | --pacPort port Port to serve PAC and other static files (default 8080) 17 | --limitHosts Only proxy hosts in the currently-selected userscript set 18 | (use in conjunction with set-specific PAC files) 19 | --addPac file Path to existing PAC file to add proxy rules to 20 | --proxyIndex file Path to proxy index HTML file 21 | --auth Enable proxy authentication (experimental) 22 | --overrideHost Override domain name that was automatically discovered`); 23 | return; 24 | } 25 | 26 | //Read config file from file specified in command-line arguments 27 | var config = JSON.parse(fs.readFileSync(argv['_'][0], 'utf8')); 28 | //Read proxy index HTML file if argument is provided 29 | if (argv['proxyIndex'] && argv['proxyIndex'] !== true) { 30 | argv['proxyIndex'] = fs.readFileSync(argv['proxyIndex'], 'utf8'); 31 | } 32 | //Read PAC file to add proxy rules to, if argument is provided 33 | if (argv['addPac'] && argv['addPac'] !== true) { 34 | argv['addPac'] = fs.readFileSync(argv['addPac'], 'utf8'); 35 | } 36 | 37 | //Start the proxy 38 | proxy(config, argv); 39 | -------------------------------------------------------------------------------- /config/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Select Userscript 4 | 5 | 58 | 127 | 128 | 129 |

Userscript Proxy

130 |

Select Userscript

131 |
    132 | @@USERSCRIPT-PROXY-LIST@@ 133 |
134 |

Quick Start

135 |
    136 |
  1. 137 | Add Userscript Proxy's HTTP Proxy to your device settings 138 | 141 |
  2. 142 |
  3. 143 | Install Userscript Proxy's CA Certificate 144 | 147 |
  4. 148 |
149 |

Detailed Instructions

150 |
    151 |
  • Android 7
    152 |
      153 |
    • Download the CA Certificate
    • 154 |
    • Enter your device PIN, if applicable
    • 155 |
    • Name the certificate, e.g. "Userscript Proxy CA" and tap "OK"
    • 156 |
    • Copy the Automatic HTTP Proxy URL to the clipboard
    • 157 |
    • Open the "Settings" app and open your Wi-Fi settings
    • 158 |
    • Long press on the wireless network you're connected to, and tap "Manage network settings"
    • 159 |
    • Tap "Show advanced options"
    • 160 |
    • If there are any Proxy settings other than "None", make note of them so that you can re-enter them after you are done using this proxy
    • 161 |
    • Under Proxy, select "Auto-config" and paste the HTTP Proxy URL into the "PAC web address" field
    • 162 |
    • Tap "Save"
    • 163 |
    • Quit and re-open the Chrome browser
    • 164 |
    165 |
  • 166 | 167 |
  • iOS
    168 |
      169 |
    • Download the CA Certificate
    • 170 |
    • Tap "Allow" to open Settings 171 |
    • Tap "Install" and enter your device PIN, if applicable
    • 172 |
    • You will see a warning. Userscript Proxy is self-signed, so this is expected. Tap "Install" again
    • 173 |
    • Tap "Install" a third time
    • 174 |
    • Tap "Done"
    • 175 |
    • Open the "Settings" app and go to General > About > Certificate Trust Settings
    • 176 |
    • Under "Enable full trust for root certificates", turn on trust for the NodeMITMProxyCA certificate and tap "Continue." Userscript Proxy needs to be able to intercept and alter your connection to websites to function
    • 177 |
    • Copy the Automatic HTTP Proxy URL to the clipboard
    • 178 |
    • Open the "Settings" app and go to "Wi-Fi"
    • 179 |
    • Tap the i icon to the right of the currently selected Wi-Fi network
    • 180 |
    • Under "HTTP Proxy", tap "Configure Proxy"
    • 181 |
    • If there are any settings here other than "Off", make note of them so that you can re-enter them after you are done using this proxy
    • 182 |
    • Tap "Automatic"
    • 183 |
    • Paste the HTTP Proxy URL into the "URL" field and tap "Save"
    • 184 |
    • Quit and re-open the Safari browser
    • 185 |
    186 |
  • 187 |
  • Mac
    188 |
      189 |
    • Download the CA Certificate
    • 190 |
    • Open the downloaded certificate file
    • 191 |
    • Click "Add" to add the certificate to the login keychain
    • 192 |
    • Search for "NodeMITMProxyCA" in the Keychain Access window that appears
    • 193 |
    • Double-click the certificate in the search results list
    • 194 |
    • Open the "Trust" dropdown
    • 195 |
    • Under the "When using this certificate" dropdown, choose "Always Trust"
    • 196 |
    • Close the window, enter your computer's password in the window that appears, and click "Update Settings"
    • 197 |
    • Copy the Automatic HTTP Proxy URL to the clipboard
    • 198 |
    • Open System Preferences
    • 199 |
    • Click the "Network" preference pane
    • 200 |
    • Click the currently-connected network interface in the list on the left side of the window
    • 201 |
    • Click "Advanced..." in the lower-right corner
    • 202 |
    • Click the "Proxies" tab
    • 203 |
    • Make sure "Automatic Proxy Configuration" is checked
    • 204 |
    • If there is a Proxy Configuration File URL already entered, save it so you can re-enter it after you are done using this proxy
    • 205 |
    • Paste the HTTP Proxy URL from the proxy web page into the "URL" field
    • 206 |
    • Click "OK"
    • 207 |
    • Click "Apply"
    • 208 |
    • Quit and re-open your web browser
    • 209 |
    210 |
  • 211 |
  • Windows 7
    212 |
      213 |
    • Download the CA Certificate
    • 214 |
    • Open the downloaded certificate file. Click "Open" if you get a security warning
    • 215 |
    • Click "Install Certificate..."
    • 216 |
    • Click "Next" in the Certificate Import Wizard
    • 217 |
    • Click "Place all certificates in the following store", and click "Browse..."
    • 218 |
    • Select "Trusted Root Certification Authorities" and click "OK"
    • 219 |
    • Click "Next"
    • 220 |
    • Click "Finish", then "Yes", then "OK"
    • 221 |
    • Copy the Automatic HTTP Proxy URL to the clipboard
    • 222 |
    • Open the Start menu and click "Control Panel"
    • 223 |
    • Search for "proxy"
    • 224 |
    • Click "Configure proxy server"
    • 225 |
    • Click "LAN settings"
    • 226 |
    • Make sure the "Use automatic configuration script" checkbox is enabled
    • 227 |
    • If there is a script address already entered, save it so you can re-enter it after you are done using this proxy
    • 228 |
    • Paste the HTTP Proxy URL from the proxy web page into the "Address" field
    • 229 |
    • Click "OK"
    • 230 |
    • Click "OK"
    • 231 |
    • Quit and re-open your web browser
    • 232 |
    233 |
  • 234 |
  • Windows 10
    235 |
      236 |
    • Download the CA Certificate
    • 237 |
    • Open the downloaded certificate file
    • 238 |
    • Click "Install Certificate..."
    • 239 |
    • Click "Next" in the Certificate Import Wizard
    • 240 |
    • Click "Place all certificates in the following store", and click "Browse..."
    • 241 |
    • Select "Trusted Root Certification Authorities" and click "OK"
    • 242 |
    • Click "Next"
    • 243 |
    • Click "Finish", then "Yes", then "OK"
    • 244 |
    • Copy the Automatic HTTP Proxy URL to the clipboard
    • 245 |
    • Open the Start menu and click "Settings"
    • 246 |
    • Click "Network & Internet"
    • 247 |
    • Click "Proxy"
    • 248 |
    • Make sure the "Use setup script" toggle is enabled
    • 249 |
    • If there is a script address already entered, save it so you can re-enter it after you are done using this proxy
    • 250 |
    • Paste the HTTP Proxy URL from the proxy web page into the "Script address" field
    • 251 |
    • Click "Save"
    • 252 |
    • Quit and re-open your web browser
    • 253 |
    254 |
  • 255 |
256 |

Removal Instructions

257 |
    258 |
  • Android 7
    259 |
      260 |
    • Open the "Settings" app and search for "User certificates"
    • 261 |
    • Tap "Userscript Proxy CA" (or whatever you named it when you installed it)
    • 262 |
    • Tap "Remove"
    • 263 |
    • Go back to Settings Home and open your Wi-Fi settings
    • 264 |
    • Long press on the wireless network you're connected to, and tap "Manage network settings"
    • 265 |
    • Under "Show advanced options", change "Proxy Auto-config" to either "None" or any other settings that you saved when you set up the proxy
    • 266 |
    • Tap "Save"
    • 267 |
    268 |
  • 269 | 270 |
  • iOS
    271 |
      272 |
    • Open the "Settings" app and tap "General", then "Profiles"
    • 273 |
    • Tap "NodeMITMProxyCA"
    • 274 |
    • Tap "Remove Profile"
    • 275 |
    • If prompted, enter your device PIN, then tap "Remove"
    • 276 |
    • Go back to the main screen of the Settings app and tap "Wi-Fi"
    • 277 |
    • Tap the i icon to the right of the currently selected Wi-Fi network
    • 278 |
    • Under "HTTP Proxy", tap "Configure Proxy"
    • 279 |
    • Change this to either "Off" or any other settings that you saved when you set up the proxy, then tap "Save"
    • 280 |
    281 |
  • 282 |
  • Mac
    283 |
      284 |
    • Open the Keychain Access app (in /Applications/Utilities)
    • 285 |
    • Search for "NodeMITMProxyCA" in the Keychain Access window that appears
    • 286 |
    • Click the certificate in the search results list
    • 287 |
    • Press the "Delete" key
    • 288 |
    • Click the "Delete" button, enter your computer's password in the window that appears, and click "Update Settings"
    • 289 |
    • Open System Preferences
    • 290 |
    • Click the "Network" preference pane
    • 291 |
    • Click the currently-connected network interface in the list on the left side of the window
    • 292 |
    • Click "Advanced..." in the lower-right corner
    • 293 |
    • Click the "Proxies" tab
    • 294 |
    • Uncheck "Automatic Proxy Configuration"
    • 295 |
    • If there was a Proxy Configuration File URL already selected before you set up this proxy, leave the box checked, click "Automatic Proxy Configuration", and replace the URL with the one you saved when you set up this proxy
    • 296 |
    • Click "OK"
    • 297 |
    • Click "Apply"
    • 298 |
    • Quit and re-open your web browser
    • 299 |
    300 |
  • 301 |
  • Windows 7
    302 |
      303 |
    • Open the Start menu, type "mmc", then press Enter
    • 304 |
    • Click "Yes" if prompted about letting Microsoft Management Console make changes to your computer
    • 305 |
    • In the "File" menu, select "Add/Remove Snap-in..."
    • 306 |
    • Double-click "Certificates", select "My user account", then click "Finish"
    • 307 |
    • Click "OK"
    • 308 |
    • In the list on the left, double-click "Certificates", then "Trusted Root Certification Authorities", then "Certificates"
    • 309 |
    • Click "NodeMITMProxyCA" and click the Delete button (red X) in the toolbar at the top of the window
    • 310 |
    • Click "Yes", then "Yes"
    • 311 |
    • Open the Start menu and click "Control Panel"
    • 312 |
    • Search for "proxy"
    • 313 |
    • Click "Configure proxy server"
    • 314 |
    • Click "LAN settings"
    • 315 |
    • Turn off the checkbox for "Use automatic configuration script"
    • 316 |
    • If there was an automatic configuration script already selected before you set up this proxy, leave the checkbox enabled and replace the URL with the one you saved when you set up this proxy
    • 317 |
    • Click "OK"
    • 318 |
    • Click "OK"
    • 319 |
    • Quit and re-open your web browser
    • 320 |
    321 |
  • 322 |
  • Windows 10
    323 |
      324 |
    • Open the Start menu and click "Settings"
    • 325 |
    • Search for "Manage user certificates" and click it in the search results list
    • 326 |
    • Double click "Trusted Root Certification Authorities", then "Certificates" in the list on the left
    • 327 |
    • Click "NodeMITMProxyCA" and click the Delete button (red X) in the toolbar at the top of the window
    • 328 |
    • Click "Yes", then "Yes"
    • 329 |
    • Open the Start menu and click "Settings"
    • 330 |
    • Click "Network & Internet"
    • 331 |
    • Click "Proxy"
    • 332 |
    • Turn off the toggle for "Use setup script"
    • 333 |
    • If there was a setup script already selected before you set up this proxy, leave the toggle enabled and replace the URL with the one you saved when you set up this proxy
    • 334 |
    • Quit and re-open your web browser
    • 335 |
    336 |
  • 337 |
338 | 339 | 340 | -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "userscripts": [ 3 | { 4 | "title": "Alter background color", 5 | "id": "alterBackground", 6 | "match": [ 7 | {"host": "ebay.com", "url": "/"}, 8 | {"host": "www.ebay.com", "url": "/"} 9 | ], 10 | "scripts": [ 11 | "./scripts/background.js" 12 | ], 13 | "styles": [ 14 | "./styles/background.css" 15 | ] 16 | }, 17 | { 18 | "title": "Alter Google", 19 | "id": "googleResults", 20 | "match": [ 21 | {"host": "www.google.com", "url": "/"} 22 | ], 23 | "scripts": [ 24 | "./scripts/google.js" 25 | ], 26 | "styles": [ 27 | "./styles/google.css" 28 | ] 29 | } 30 | ], 31 | "userscriptSets": [ 32 | { 33 | "title": "Alter Google results and eBay background color", 34 | "id": "searchResultsBackground", 35 | "password": "pass", 36 | "userscripts": ["alterBackground","googleResults"] 37 | }, 38 | { 39 | "title": "Alter Google results", 40 | "id": "searchResults", 41 | "password": "pass", 42 | "userscripts": ["googleResults"] 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /examples/scripts/background.js: -------------------------------------------------------------------------------- 1 | // This example changes the background color of eBay's home page to red (see also: styles/background.css) 2 | console.log('Userscript proxy activated - alter background color'); 3 | -------------------------------------------------------------------------------- /examples/scripts/google.js: -------------------------------------------------------------------------------- 1 | // This example modifies Google search result pages and adds a link to an eBay search as the top result 2 | (function() { 3 | console.log('Userscript proxy activated - Alter Google results'); 4 | var search = undefined; 5 | var query; 6 | var queryComponent; 7 | var queryDecoded; 8 | window.setInterval(function() { 9 | if (document.body.classList.contains('srp') != -1) { 10 | 11 | query = window.location.href.match(/[?&#]q=([^&]*)/); 12 | queryComponent = query[1]; 13 | 14 | //Google could always change their website styling, so this may only work for this snapshot in time 15 | //Still, this example should give you an idea about how to add elements to a page, based on a query 16 | if (search != queryComponent && document.getElementById('center_col')) { 17 | queryDecoded = decodeURIComponent(query[1].replace(/\+/g, ' ')); 18 | search = queryComponent; 19 | 20 | var div = document.createElement('div'); 21 | div.setAttribute("id", "ebay-add-search-result"); 22 | div.setAttribute("class", "mnr-c"); 23 | 24 | var url = "http://www.ebay.com/sch/i.html?_nkw="+queryComponent; 25 | var output; 26 | 27 | //Use different styling based on Google's mobile/desktop breakpoint 28 | if (document.getElementById('rcnt')) { //Desktop 29 | output = ` 30 |
31 |
32 |
33 |

`+queryDecoded+` | eBay

34 |
35 |
36 |
eBay › ... › `+queryDecoded+`
37 | Buy `+queryDecoded+` on eBay 38 |
39 |
40 |
41 |
42 |
`; 43 | } else { //Mobile 44 | output = ` 45 |
46 |
47 |
48 |
49 | 50 |
eBay › ... › `+queryDecoded+`
51 |
52 |
53 |
Buy `+queryDecoded+` on eBay
54 |
55 |
56 |
57 |
`; 58 | } 59 | 60 | div.innerHTML = output; 61 | if (document.getElementById("ebay-add-search-result")) { 62 | element = document.getElementById("ebay-add-search-result"); 63 | element.parentNode.removeChild(element); 64 | } 65 | document.getElementById('center_col').insertBefore(div, document.getElementById('center_col').firstChild); 66 | } 67 | } 68 | }, 100); 69 | })(); 70 | -------------------------------------------------------------------------------- /examples/styles/background.css: -------------------------------------------------------------------------------- 1 | /* This example changes the background color of eBay's home page to red */ 2 | html > body { 3 | background-color: red!important; 4 | background-image: none!important; 5 | } 6 | -------------------------------------------------------------------------------- /examples/styles/google.css: -------------------------------------------------------------------------------- 1 | /* This example modifies Google search result pages and adds a link to an eBay search as the top result (no CSS modifications were necessary, but they could be in this file) */ 2 | -------------------------------------------------------------------------------- /lib/userscript-proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Proxy = require('http-mitm-proxy'); 4 | var fs = require('fs'); 5 | var node_fqdn = require('node-fqdn'); 6 | var os = require('os'); 7 | var express = require('express'); 8 | var forge = require('node-forge'); 9 | 10 | module.exports = function(config, options) { 11 | options = typeof options !== 'undefined' ? options : {}; 12 | var currentId = options['loadScript'] && options['loadScript'] !== true ? options['loadScript'] : false; //userscript set that is initially active 13 | var proxyPort = options['proxyPort'] && options['proxyPort'] !== true ? options['proxyPort'] : 8888; //port to run proxy from 14 | var pacPort = options['pacPort'] && options['pacPort'] !== true ? options['pacPort'] : 8080; //port to serve PAC files and other static files from 15 | var enableProxyAuth = !!options['auth']; //enable basic authentication 16 | var limitHosts = !!options['limitHosts']; //limit allowed hosts to current userscript set 17 | var internalProxy = options['addPac'] && options['addPac'] !== true ? options['addPac'] : false; //add proxy rules to the beginning of an existing PAC file 18 | var proxyIndex = options['proxyIndex'] && options['proxyIndex'] !== true ? options['proxyIndex'] : fs.readFileSync(__dirname + '/../config/index.html', 'utf8'); //proxy settings index HTML file 19 | var staticDir = options['staticDir'] && options['staticDir'] !== true ? options['staticDir'] : false; //path of directory to serve with static files 20 | var overrideHost = options['overrideHost'] && options['overrideHost'] !== true ? options['overrideHost'] : false; //override domain name that the proxy automatically discovers 21 | 22 | var userscripts = config.userscripts; //all userscripts from config file 23 | var userscriptSets = config.userscriptSets; //all userscript sets from config file 24 | var authSelectedBasic = {}; //map of basic authentication values to userscript sets 25 | var authSelectedById = {}; //map of userscript set IDs to userscript sets 26 | var userscriptSetIds = []; //all userscript set IDs 27 | var allHosts = []; //all hosts to proxy from config file 28 | var auth = ""; //currently active basic auth string 29 | 30 | var hostname = os.hostname(); 31 | var fqdn; 32 | if (overrideHost) { 33 | fqdn = overrideHost 34 | } else { 35 | //getting the FQDN sometimes errors out, so fall back gracefully to the hostname 36 | try { 37 | fqdn = node_fqdn(); 38 | } catch (e) { 39 | fqdn = hostname; 40 | } 41 | } 42 | 43 | //All instances of the following static keys will be replaced with their corresponding dynamic values in userscripts 44 | var textReplace = [ 45 | { 46 | "key": /@@USERSCRIPT-PROXY-HOSTNAME-FQDN@@/g, 47 | "value": fqdn 48 | }, 49 | { 50 | "key": /@@USERSCRIPT-PROXY-HOSTNAME@@/g, 51 | "value": hostname 52 | }, 53 | { 54 | "key": /@@USERSCRIPT-PROXY-PAC-PORT@@/g, 55 | "value": pacPort 56 | }, 57 | { 58 | "key": /@@USERSCRIPT-PROXY-PROXY-PORT@@/g, 59 | "value": proxyPort 60 | }, 61 | { 62 | "key": /@@USERSCRIPT-PROXY-STATIC-SERVER@@/g, 63 | "value": "http://"+fqdn+":"+pacPort+"/static" 64 | }, 65 | { 66 | "key": /@@USERSCRIPT-PROXY-PAC-URL@@/g, 67 | "value": "http://"+fqdn+":"+pacPort+"/userscript-proxy-all.pac" 68 | }, 69 | { 70 | "key": /@@USERSCRIPT-PROXY-CA-URL@@/g, 71 | "value": "http://"+fqdn+":"+pacPort+"/"+hostname+"-ca.crt" 72 | }, 73 | { 74 | "key": /@@USERSCRIPT-PROXY-LIST@@/g, //HTML list of userscript sets 75 | "value": function() { 76 | var list = ""; 77 | list += '
  • ' + (!currentId ? '' : '') + 'None' + (!currentId ? '' : '') + '
  • \n'; 78 | for (var a in userscriptSets) { 79 | list += '
  • ' + (userscriptSets[a]['id'] == currentId ? '' : '') + '' + userscriptSets[a]['title'] + '' + (userscriptSets[a]['id'] == currentId ? '' : '') + '
  • \n'; 80 | } 81 | return list; 82 | } 83 | } 84 | ]; 85 | 86 | //Replace all static keys with dynamic values in the given text string 87 | function replaceAllText(file, replaceStrings) { 88 | for (var i in replaceStrings) { 89 | file = file.replace(replaceStrings[i].key, replaceStrings[i].value); 90 | } 91 | return file; 92 | } 93 | 94 | //Generate script and stylesheet tags to add to matched pages ('scriptsHtml' and 'stylesHtml' in the 'userscripts' variable) 95 | function generateScriptAndStyleHTML() { 96 | for (var s in userscripts) { 97 | userscripts[s]['scriptsHtml'] = ''; 98 | userscripts[s]['stylesHtml'] = ''; 99 | for (var script in userscripts[s]['scripts']) { 100 | if (userscripts[s]['scripts'][script].indexOf('./') == 0) { 101 | var currentFile = replaceAllText(fs.readFileSync(userscripts[s]['scripts'][script].replace('./', process.cwd() + '/'), 'utf8'), textReplace); 102 | userscripts[s]['scriptsHtml'] += '\n'; 103 | } else { 104 | userscripts[s]['scriptsHtml'] += '\n'; 105 | } 106 | } 107 | for (var style in userscripts[s]['styles']) { 108 | if (userscripts[s]['styles'][style].indexOf('./') == 0) { 109 | var currentFile = replaceAllText(fs.readFileSync(userscripts[s]['styles'][style].replace('./', process.cwd() + '/'), 'utf8'), textReplace); 110 | userscripts[s]['stylesHtml'] += '\n'; 111 | } else { 112 | userscripts[s]['stylesHtml'] += '\n'; 113 | } 114 | } 115 | } 116 | } 117 | generateScriptAndStyleHTML(); 118 | fs.watch(process.cwd() + '/', {recursive: true}, function(eventType, filename) { 119 | console.log('Reloading scripts and styles.'+(filename ? ' ' + filename + ' changed.':'')); 120 | generateScriptAndStyleHTML(); 121 | }); 122 | 123 | //Generate key/value pairs for authentication values to userscript sets 124 | for (var a in userscriptSets) { 125 | authSelectedBasic[userscriptSets[a].id+':'+userscriptSets[a].password] = userscriptSets[a].userscripts; //map basic authentication values to userscript set 126 | authSelectedById[userscriptSets[a].id] = userscriptSets[a].userscripts; //map userscript set ID to userscript set 127 | userscriptSetIds.push(userscriptSets[a].id); //all userscript set IDs 128 | } 129 | 130 | //Get all hosts in the config file for a given userscript ID 131 | function getHosts(id) { 132 | var hosts = []; 133 | var selected = []; 134 | for (var a in userscriptSets) { 135 | if (userscriptSets[a].id == id) { 136 | selected = userscriptSets[a].userscripts; 137 | } 138 | } 139 | for (var s in userscripts) { 140 | if (selected.indexOf(userscripts[s]['id']) != -1 || id == 'all') { 141 | for (var m in userscripts[s]['match']) { 142 | if (hosts.indexOf(userscripts[s]['match'][m]['host']) == -1) { 143 | hosts.push(userscripts[s]['match'][m]['host']); 144 | } 145 | } 146 | } 147 | } 148 | return Array.from(new Set(hosts)); //return unique values 149 | } 150 | 151 | //Get all hosts in the config file for all userscripts 152 | allHosts = getHosts('all'); 153 | 154 | //Set up http-mitm-proxy 155 | var proxy = Proxy(); 156 | proxy.use(Proxy.gunzip); 157 | 158 | proxy.onError(function(ctx, err, errorKind) { 159 | var url = (ctx && ctx.clientToProxyRequest) ? ctx.clientToProxyRequest.url : ''; 160 | console.error(errorKind + ' on ' + url + ':', err); 161 | }); 162 | 163 | //Authenticate on connect for HTTPS connections (only if auth is enabled) 164 | proxy.onConnect(function(req, socket, head, callback) { 165 | if (enableProxyAuth) { 166 | if (req.headers['proxy-authorization'] || req.headers['Proxy-Authorization']) { 167 | var encodedAuth = req.headers['proxy-authorization'] || req.headers['Proxy-Authorization']; 168 | encodedAuth = encodedAuth.substr(6); 169 | var currentAuth = new Buffer(encodedAuth, 'base64').toString("ascii"); 170 | console.log("current auth connect",currentAuth); 171 | if (authSelectedBasic[currentAuth]) { 172 | auth = currentAuth; 173 | console.log("header set connect",req.headers.host); 174 | return callback(); 175 | } else { 176 | console.log("connect unauthorized"); 177 | } 178 | } 179 | auth = ""; 180 | console.log("header not set connect",req.headers.host); 181 | socket.write('HTTP/1.1 407 Proxy Authentication Required\r\n'); 182 | socket.write('Proxy-Authenticate: Basic realm="userscriptproxy"\r\n'); 183 | socket.write('\r\n'); 184 | socket.end(); 185 | } else { 186 | return callback(); 187 | } 188 | }); 189 | 190 | //Authenticate when sending response headers for HTTP connections (only if auth is enabled) 191 | // see https://github.com/joeferner/node-http-mitm-proxy/issues/74#issuecomment-195967863 192 | proxy.onResponseHeaders(function(ctx, callback) { 193 | if (enableProxyAuth) { 194 | if (!ctx.isSSL) { 195 | var correctAuth = false; 196 | if (ctx.clientToProxyRequest.headers['proxy-authorization'] || ctx.clientToProxyRequest.headers['Proxy-Authorization']) { 197 | var encodedAuth = ctx.clientToProxyRequest.headers['proxy-authorization'] || ctx.clientToProxyRequest.headers['Proxy-Authorization']; 198 | encodedAuth = encodedAuth.substr(6); 199 | var currentAuth = new Buffer(encodedAuth, 'base64').toString("ascii"); 200 | if (authSelectedBasic[currentAuth]) { 201 | console.log("header set response",ctx.clientToProxyRequest.headers.host,ctx.clientToProxyRequest.url); 202 | return callback(); 203 | } 204 | } 205 | console.log("header not set response",ctx.clientToProxyRequest.headers.host,ctx.clientToProxyRequest.url); 206 | ctx.proxyToClientResponse.setHeader('Proxy-Authenticate', 'Basic realm="userscriptproxy"'); 207 | ctx.proxyToClientResponse.writeHead(407, 'Proxy Authentication Required'); 208 | ctx.proxyToClientResponse.end("Access denied"); 209 | } else { 210 | return callback(); 211 | } 212 | } else { 213 | return callback(); 214 | } 215 | }); 216 | 217 | //Alter responses to requests to the proxy based on what userscripts are selected 218 | proxy.onRequest(function(ctx, callback) { 219 | var fullUrl = ctx.clientToProxyRequest.headers.host + ctx.clientToProxyRequest.url; 220 | var allowed = false; //is this host allowed by the proxy? 221 | var selected; //selected userscript set 222 | 223 | //Proxy all hosts in config file 224 | if (!limitHosts) { 225 | for (var h in allHosts) { 226 | if (ctx.clientToProxyRequest.headers.host.match(allHosts[h])) { 227 | allowed = true; 228 | } 229 | } 230 | } 231 | 232 | //Set selected userscript set 233 | // Use basic authentication details from config file if proxy auth is enabled 234 | // Use userscript set ID if proxy auth is not enabled 235 | if (enableProxyAuth) { 236 | var currentAuth = ""; 237 | if (ctx.isSSL) { 238 | currentAuth = auth; 239 | auth = ""; 240 | } else { 241 | var encodedAuth = ctx.clientToProxyRequest.headers['proxy-authorization'] || ctx.clientToProxyRequest.headers['Proxy-Authorization']; 242 | if (encodedAuth) { 243 | encodedAuth = encodedAuth.substr(6); 244 | currentAuth = new Buffer(encodedAuth, 'base64').toString("ascii"); 245 | } 246 | } 247 | 248 | if (currentAuth && authSelectedBasic[currentAuth]) { 249 | console.log("current auth request",currentAuth); 250 | } else { 251 | return callback(); 252 | } 253 | selected = authSelectedBasic[currentAuth]; 254 | } else { 255 | if (currentId) { 256 | selected = authSelectedById[currentId]; 257 | } else { 258 | selected = false; 259 | } 260 | } 261 | 262 | //If the host and URL match a selected userscript, alter the page that is served: add the styles and scripts from that userscript 263 | for (var s in userscripts) { 264 | var thisScript = false; //do host and URL match a selected userscript? 265 | if (selected && selected.indexOf(userscripts[s]['id']) != -1) { 266 | for (var h in userscripts[s]['match']) { 267 | if (ctx.clientToProxyRequest.headers.host.match(userscripts[s]['match'][h]['host'])) { 268 | //Only proxy hosts in selected userscript set 269 | if (limitHosts) { 270 | allowed = true; 271 | } 272 | if (ctx.clientToProxyRequest.headers.host.match(userscripts[s]['match'][h]['host'] + (userscripts[s]['match'][h]['port'] ? ':'+userscripts[s]['match'][h]['port'] : '')) && ctx.clientToProxyRequest.url.match(userscripts[s]['match'][h]['url'])) { 273 | thisScript = true; 274 | break; 275 | } 276 | } 277 | } 278 | //Host and URL match a selected userscript, so alter the page 279 | if (thisScript) { 280 | console.log("Altering with " + userscripts[s]['id'] + ": " + fullUrl); 281 | var styles = userscripts[s]['stylesHtml']; 282 | var scripts = userscripts[s]['scriptsHtml']; 283 | ctx.onResponseData(function(ctx, chunk, callback) { 284 | //only alter pages that are served with a text/* MIME type 285 | if (ctx.serverToProxyResponse.headers['content-type'].indexOf('text/') != -1) { 286 | var chunkString = chunk.toString(); 287 | chunkString = chunkString.replace('', styles+''); 288 | chunkString = chunkString.replace('', scripts+''); 289 | chunk = new Buffer(chunkString); 290 | } 291 | return callback(null, chunk); 292 | }); 293 | } 294 | } 295 | } 296 | 297 | //Drop the connection if the host is not specified in any userscripts 298 | if (allowed) { 299 | console.log("Proxying: " + fullUrl); 300 | return callback(); 301 | } else { 302 | console.log("Disallowed: " + fullUrl); 303 | ctx.proxyToClientResponse.end('Proxy not allowed for this host'); 304 | } 305 | }); 306 | 307 | //Start proxy server 308 | proxy.listen({port: proxyPort, sslCaDir: os.homedir()+'/.userscript-proxy/'}); 309 | console.log('Proxy listening on port ' + proxyPort); 310 | 311 | //Set up web server for PAC and static files 312 | var app = express(); 313 | 314 | //Generate and serve a proxy PAC file for the given userscript set ID 315 | function getProxyPac(id) { 316 | var matchPac = ' var matchHosts = '+JSON.stringify(getHosts(id))+';'; 317 | matchPac += ` 318 | for (var h in matchHosts) { 319 | if (dnsDomainIs(host,matchHosts[h])) { 320 | return 'PROXY `+fqdn+`:`+proxyPort+`'; 321 | } 322 | }`; 323 | 324 | var externalProxy = ` 325 | function FindProxyForURL(url, host) { 326 | ` + matchPac + ` 327 | return 'DIRECT'; 328 | }` 329 | 330 | if (internalProxy) { 331 | //Add rules for this proxy to the beginning of the existing proxy PAC file 332 | //This may not work with all edge cases (e.g. if the FindProxyForURL function is inside a comment) 333 | var currentInternalProxy = internalProxy.replace(/function\s+FindProxyForURL\s*\(\s*(\w+)\s*,\s*(\w+)\s*\)\s*\{/, "function FindProxyForURL($1, $2) {\n"+matchPac+"\n"); 334 | } 335 | 336 | app.get('/userscript-proxy-'+id+'.pac', function(req, res) { 337 | res.type('application/x-ns-proxy-autoconfig'); 338 | res.send(externalProxy); 339 | }); 340 | 341 | if (internalProxy) { 342 | app.get('/userscript-proxy-'+id+'-internal.pac', function(req, res) { 343 | res.type('application/x-ns-proxy-autoconfig'); 344 | res.send(currentInternalProxy); 345 | }); 346 | } 347 | } 348 | 349 | //Serve proxy PAC files for each userscript set IDs and all IDs 350 | for (var i in userscriptSetIds) { 351 | getProxyPac(userscriptSetIds[i]); 352 | } 353 | getProxyPac('all'); 354 | 355 | //Serve the base web page for proxy configuration 356 | app.get('/', function(req, res) { 357 | console.log(req.url); 358 | //Set the current userscript set ID based on search string 359 | if (req.url.indexOf('/?') == 0) { 360 | var search = req.url.substr(2); 361 | if (!search) { 362 | currentId = false; 363 | } else if (authSelectedById[search]) { 364 | currentId = search; 365 | } 366 | } 367 | var result = replaceAllText(proxyIndex, textReplace); //replace static keys with dynamic values from the proxy index HTML file 368 | res.send(result); 369 | }); 370 | 371 | //Serve the CA certificate for this proxy server 372 | app.get('/'+hostname+'-ca.crt', function(req, res) { 373 | //Convert the PEM certificate to DER format 374 | var pemcert = fs.readFileSync(os.homedir()+'/.userscript-proxy/certs/ca.pem'); 375 | var cert = forge.pki.certificateFromPem(pemcert); 376 | var asn1cert = forge.pki.certificateToAsn1(cert); 377 | var dercert = forge.asn1.toDer(asn1cert); 378 | 379 | res.type('application/x-x509-ca-cert'); 380 | res.end(dercert.getBytes(), 'binary'); 381 | }); 382 | 383 | //Serve static files within the 'static' directory 384 | if (staticDir) { 385 | app.use('/static', express.static(staticDir)); 386 | } 387 | 388 | //Start web server for PAC and static files 389 | app.listen(pacPort, function() { 390 | console.log('Web server listening on port ' + pacPort); 391 | console.log('Load http://'+fqdn+':'+pacPort+'/ in your web browser to view and change proxy settings'); 392 | }); 393 | }; 394 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userscript-proxy", 3 | "version": "0.1.8", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "~2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "async": { 22 | "version": "2.6.1", 23 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", 24 | "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", 25 | "requires": { 26 | "lodash": "^4.17.10" 27 | } 28 | }, 29 | "async-limiter": { 30 | "version": "1.0.0", 31 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 32 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 33 | }, 34 | "bindings": { 35 | "version": "1.2.1", 36 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", 37 | "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" 38 | }, 39 | "body-parser": { 40 | "version": "1.18.3", 41 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 42 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 43 | "requires": { 44 | "bytes": "3.0.0", 45 | "content-type": "~1.0.4", 46 | "debug": "2.6.9", 47 | "depd": "~1.1.2", 48 | "http-errors": "~1.6.3", 49 | "iconv-lite": "0.4.23", 50 | "on-finished": "~2.3.0", 51 | "qs": "6.5.2", 52 | "raw-body": "2.3.3", 53 | "type-is": "~1.6.16" 54 | } 55 | }, 56 | "bytes": { 57 | "version": "3.0.0", 58 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 59 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 60 | }, 61 | "content-disposition": { 62 | "version": "0.5.2", 63 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 64 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 65 | }, 66 | "content-type": { 67 | "version": "1.0.4", 68 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 69 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 70 | }, 71 | "cookie": { 72 | "version": "0.3.1", 73 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 74 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 75 | }, 76 | "cookie-signature": { 77 | "version": "1.0.6", 78 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 79 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 80 | }, 81 | "deasync": { 82 | "version": "0.1.13", 83 | "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.13.tgz", 84 | "integrity": "sha512-/6ngYM7AapueqLtvOzjv9+11N2fHDSrkxeMF1YPE20WIfaaawiBg+HZH1E5lHrcJxlKR42t6XPOEmMmqcAsU1g==", 85 | "requires": { 86 | "bindings": "~1.2.1", 87 | "nan": "^2.0.7" 88 | } 89 | }, 90 | "debug": { 91 | "version": "2.6.9", 92 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 93 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 94 | "requires": { 95 | "ms": "2.0.0" 96 | } 97 | }, 98 | "depd": { 99 | "version": "1.1.2", 100 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 101 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 102 | }, 103 | "destroy": { 104 | "version": "1.0.4", 105 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 106 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 107 | }, 108 | "ee-first": { 109 | "version": "1.1.1", 110 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 111 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 112 | }, 113 | "encodeurl": { 114 | "version": "1.0.2", 115 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 116 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 117 | }, 118 | "escape-html": { 119 | "version": "1.0.3", 120 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 121 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 122 | }, 123 | "etag": { 124 | "version": "1.8.1", 125 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 126 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 127 | }, 128 | "express": { 129 | "version": "4.16.4", 130 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", 131 | "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", 132 | "requires": { 133 | "accepts": "~1.3.5", 134 | "array-flatten": "1.1.1", 135 | "body-parser": "1.18.3", 136 | "content-disposition": "0.5.2", 137 | "content-type": "~1.0.4", 138 | "cookie": "0.3.1", 139 | "cookie-signature": "1.0.6", 140 | "debug": "2.6.9", 141 | "depd": "~1.1.2", 142 | "encodeurl": "~1.0.2", 143 | "escape-html": "~1.0.3", 144 | "etag": "~1.8.1", 145 | "finalhandler": "1.1.1", 146 | "fresh": "0.5.2", 147 | "merge-descriptors": "1.0.1", 148 | "methods": "~1.1.2", 149 | "on-finished": "~2.3.0", 150 | "parseurl": "~1.3.2", 151 | "path-to-regexp": "0.1.7", 152 | "proxy-addr": "~2.0.4", 153 | "qs": "6.5.2", 154 | "range-parser": "~1.2.0", 155 | "safe-buffer": "5.1.2", 156 | "send": "0.16.2", 157 | "serve-static": "1.13.2", 158 | "setprototypeof": "1.1.0", 159 | "statuses": "~1.4.0", 160 | "type-is": "~1.6.16", 161 | "utils-merge": "1.0.1", 162 | "vary": "~1.1.2" 163 | } 164 | }, 165 | "finalhandler": { 166 | "version": "1.1.1", 167 | "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 168 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 169 | "requires": { 170 | "debug": "2.6.9", 171 | "encodeurl": "~1.0.2", 172 | "escape-html": "~1.0.3", 173 | "on-finished": "~2.3.0", 174 | "parseurl": "~1.3.2", 175 | "statuses": "~1.4.0", 176 | "unpipe": "~1.0.0" 177 | } 178 | }, 179 | "forwarded": { 180 | "version": "0.1.2", 181 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 182 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 183 | }, 184 | "fresh": { 185 | "version": "0.5.2", 186 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 187 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 188 | }, 189 | "http-errors": { 190 | "version": "1.6.3", 191 | "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 192 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 193 | "requires": { 194 | "depd": "~1.1.2", 195 | "inherits": "2.0.3", 196 | "setprototypeof": "1.1.0", 197 | "statuses": ">= 1.4.0 < 2" 198 | } 199 | }, 200 | "http-mitm-proxy": { 201 | "version": "0.6.1", 202 | "resolved": "https://registry.npmjs.org/http-mitm-proxy/-/http-mitm-proxy-0.6.1.tgz", 203 | "integrity": "sha512-E5VWs6mecrDsxT3SqBDymeUY0Go0r/n6v5DU6ecqTFv4lCcRP1uxkH+ebOd9nJ/3Tr4jM2GwdIuy/qCStCCu9w==", 204 | "requires": { 205 | "async": "^2.5.0", 206 | "mkdirp": "^0.5.1", 207 | "node-forge": "^0.7.1", 208 | "optimist": "^0.6.1", 209 | "semaphore": "^1.1.0", 210 | "ws": "^3.2.0" 211 | } 212 | }, 213 | "iconv-lite": { 214 | "version": "0.4.23", 215 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 216 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 217 | "requires": { 218 | "safer-buffer": ">= 2.1.2 < 3" 219 | } 220 | }, 221 | "inherits": { 222 | "version": "2.0.3", 223 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 224 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 225 | }, 226 | "ipaddr.js": { 227 | "version": "1.8.0", 228 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 229 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" 230 | }, 231 | "lodash": { 232 | "version": "4.17.11", 233 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 234 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 235 | }, 236 | "media-typer": { 237 | "version": "0.3.0", 238 | "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 239 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 240 | }, 241 | "merge-descriptors": { 242 | "version": "1.0.1", 243 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 244 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 245 | }, 246 | "methods": { 247 | "version": "1.1.2", 248 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 249 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 250 | }, 251 | "mime": { 252 | "version": "1.4.1", 253 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 254 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 255 | }, 256 | "mime-db": { 257 | "version": "1.37.0", 258 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", 259 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 260 | }, 261 | "mime-types": { 262 | "version": "2.1.21", 263 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", 264 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 265 | "requires": { 266 | "mime-db": "~1.37.0" 267 | } 268 | }, 269 | "minimist": { 270 | "version": "1.2.0", 271 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 272 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 273 | }, 274 | "mkdirp": { 275 | "version": "0.5.1", 276 | "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 277 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 278 | "requires": { 279 | "minimist": "0.0.8" 280 | }, 281 | "dependencies": { 282 | "minimist": { 283 | "version": "0.0.8", 284 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 285 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 286 | } 287 | } 288 | }, 289 | "ms": { 290 | "version": "2.0.0", 291 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 292 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 293 | }, 294 | "nan": { 295 | "version": "2.11.1", 296 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", 297 | "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" 298 | }, 299 | "negotiator": { 300 | "version": "0.6.1", 301 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 302 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 303 | }, 304 | "node-forge": { 305 | "version": "0.7.6", 306 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", 307 | "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" 308 | }, 309 | "node-fqdn": { 310 | "version": "1.1.1", 311 | "resolved": "https://registry.npmjs.org/node-fqdn/-/node-fqdn-1.1.1.tgz", 312 | "integrity": "sha1-/0jWyXfEEtMOV//flbuRPUifbRY=", 313 | "requires": { 314 | "deasync": "^0.1.7" 315 | } 316 | }, 317 | "on-finished": { 318 | "version": "2.3.0", 319 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 320 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 321 | "requires": { 322 | "ee-first": "1.1.1" 323 | } 324 | }, 325 | "optimist": { 326 | "version": "0.6.1", 327 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 328 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 329 | "requires": { 330 | "minimist": "~0.0.1", 331 | "wordwrap": "~0.0.2" 332 | }, 333 | "dependencies": { 334 | "minimist": { 335 | "version": "0.0.10", 336 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 337 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" 338 | } 339 | } 340 | }, 341 | "parseurl": { 342 | "version": "1.3.2", 343 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 344 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 345 | }, 346 | "path-to-regexp": { 347 | "version": "0.1.7", 348 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 349 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 350 | }, 351 | "proxy-addr": { 352 | "version": "2.0.4", 353 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 354 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 355 | "requires": { 356 | "forwarded": "~0.1.2", 357 | "ipaddr.js": "1.8.0" 358 | } 359 | }, 360 | "qs": { 361 | "version": "6.5.2", 362 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 363 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 364 | }, 365 | "range-parser": { 366 | "version": "1.2.0", 367 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 368 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 369 | }, 370 | "raw-body": { 371 | "version": "2.3.3", 372 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 373 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 374 | "requires": { 375 | "bytes": "3.0.0", 376 | "http-errors": "1.6.3", 377 | "iconv-lite": "0.4.23", 378 | "unpipe": "1.0.0" 379 | } 380 | }, 381 | "safe-buffer": { 382 | "version": "5.1.2", 383 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 384 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 385 | }, 386 | "safer-buffer": { 387 | "version": "2.1.2", 388 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 389 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 390 | }, 391 | "semaphore": { 392 | "version": "1.1.0", 393 | "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", 394 | "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" 395 | }, 396 | "send": { 397 | "version": "0.16.2", 398 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 399 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 400 | "requires": { 401 | "debug": "2.6.9", 402 | "depd": "~1.1.2", 403 | "destroy": "~1.0.4", 404 | "encodeurl": "~1.0.2", 405 | "escape-html": "~1.0.3", 406 | "etag": "~1.8.1", 407 | "fresh": "0.5.2", 408 | "http-errors": "~1.6.2", 409 | "mime": "1.4.1", 410 | "ms": "2.0.0", 411 | "on-finished": "~2.3.0", 412 | "range-parser": "~1.2.0", 413 | "statuses": "~1.4.0" 414 | } 415 | }, 416 | "serve-static": { 417 | "version": "1.13.2", 418 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 419 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 420 | "requires": { 421 | "encodeurl": "~1.0.2", 422 | "escape-html": "~1.0.3", 423 | "parseurl": "~1.3.2", 424 | "send": "0.16.2" 425 | } 426 | }, 427 | "setprototypeof": { 428 | "version": "1.1.0", 429 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 430 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 431 | }, 432 | "statuses": { 433 | "version": "1.4.0", 434 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 435 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 436 | }, 437 | "type-is": { 438 | "version": "1.6.16", 439 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 440 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 441 | "requires": { 442 | "media-typer": "0.3.0", 443 | "mime-types": "~2.1.18" 444 | } 445 | }, 446 | "ultron": { 447 | "version": "1.1.1", 448 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 449 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" 450 | }, 451 | "unpipe": { 452 | "version": "1.0.0", 453 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 454 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 455 | }, 456 | "utils-merge": { 457 | "version": "1.0.1", 458 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 459 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 460 | }, 461 | "vary": { 462 | "version": "1.1.2", 463 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 464 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 465 | }, 466 | "wordwrap": { 467 | "version": "0.0.3", 468 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 469 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" 470 | }, 471 | "ws": { 472 | "version": "3.3.3", 473 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 474 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 475 | "requires": { 476 | "async-limiter": "~1.0.0", 477 | "safe-buffer": "~5.1.0", 478 | "ultron": "~1.1.0" 479 | } 480 | } 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ebay/userscript-proxy", 3 | "description": "HTTP proxy to inject scripts and stylesheets into existing sites.", 4 | "version": "1.0.0", 5 | "main": "./lib/userscript-proxy.js", 6 | "bin": { 7 | "userscript-proxy": "./bin/userscript-proxy.js" 8 | }, 9 | "license": "MIT", 10 | "author": "Aaron Kleinsteiber", 11 | "contributors": [ 12 | "Tony Topper ", 13 | "Sean Gates " 14 | ], 15 | "keywords": [ 16 | "userscript", 17 | "proxy" 18 | ], 19 | "dependencies": { 20 | "express": "^4.16.3", 21 | "http-mitm-proxy": "^0.6.1", 22 | "minimist": "^1.2.0", 23 | "node-forge": "^0.7.4", 24 | "node-fqdn": "^1.1.1" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/eBay/userscript-proxy.git" 29 | }, 30 | "publishConfig": { 31 | "registry": "https://registry.npmjs.org" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/eBay/userscript-proxy/issues" 35 | }, 36 | "homepage": "https://github.com/eBay/userscript-proxy#readme", 37 | "directories": { 38 | "example": "examples", 39 | "lib": "lib" 40 | } 41 | } 42 | --------------------------------------------------------------------------------