Repository: sindresorhus/got (ts)

Issue:
Uncaught TypeError: Cannot read property 'once' of undefined     at EventEmitter.ee.once.req 
I'm attempting to use `got` from the browser with `browserify`. Looking through the issues I see others have had success, but I'm currently getting an error when using it. My request completes with the correct respsonse but an error come with it.

```
Uncaught TypeError: Cannot read property 'once' of undefined
    at EventEmitter.ee.once.req (bundle.js:10705)
    at EventEmitter.g (bundle.js:7590)
    at EventEmitter.emit (bundle.js:7521)
    at setImmediate (bundle.js:10740)
    at onNextTick (bundle.js:9657)
    at Item.run (bundle.js:8030)
    at drainQueue (bundle.js:8000)
```

The code is a fairly simple example.
```index.js
const got = require('got')
global.setImmediate = require('timers').setImmediate

const options = {
        method: 'GET'
}

got('https://api.github.com', options)
        .then(x => console.log(x.body))
        .catch(console.log)
```
I'm running this command to make my `bundle.js` file
```
browserify index.js > bundle.js
```

Have I done something incorrectly?

Pull request description:
Check req.connection exists before attaching event listeners
Fixes #427

Obviously progress events won't work in the browser, do we need to specify this in the readme?

Patch:
diff --git a/errors.js b/errors.js
new file mode 100644
index 0000000..ad83388
--- /dev/null
+++ b/errors.js
@@ -0,0 +1,92 @@
+'use strict';
+const urlLib = require('url');
+const http = require('http');
+const PCancelable = require('p-cancelable');
+const is = require('@sindresorhus/is');
+
+class GotError extends Error {
+	constructor(message, error, opts) {
+		super(message);
+		Error.captureStackTrace(this, this.constructor);
+		this.name = 'GotError';
+
+		if (!is.undefined(error.code)) {
+			this.code = error.code;
+		}
+
+		Object.assign(this, {
+			host: opts.host,
+			hostname: opts.hostname,
+			method: opts.method,
+			path: opts.path,
+			protocol: opts.protocol,
+			url: opts.href
+		});
+	}
+}
+
+module.exports.GotError = GotError;
+
+module.exports.CacheError = class extends GotError {
+	constructor(error, opts) {
+		super(error.message, error, opts);
+		this.name = 'CacheError';
+	}
+};
+
+module.exports.RequestError = class extends GotError {
+	constructor(error, opts) {
+		super(error.message, error, opts);
+		this.name = 'RequestError';
+	}
+};
+
+module.exports.ReadError = class extends GotError {
+	constructor(error, opts) {
+		super(error.message, error, opts);
+		this.name = 'ReadError';
+	}
+};
+
+module.exports.ParseError = class extends GotError {
+	constructor(error, statusCode, opts, data) {
+		super(`${error.message} in "${urlLib.format(opts)}": \n${data.slice(0, 77)}...`, error, opts);
+		this.name = 'ParseError';
+		this.statusCode = statusCode;
+		this.statusMessage = http.STATUS_CODES[this.statusCode];
+	}
+};
+
+module.exports.HTTPError = class extends GotError {
+	constructor(statusCode, statusMessage, headers, opts) {
+		if (statusMessage) {
+			statusMessage = statusMessage.replace(/\r?\n/g, ' ').trim();
+		} else {
+			statusMessage = http.STATUS_CODES[statusCode];
+		}
+		super(`Response code ${statusCode} (${statusMessage})`, {}, opts);
+		this.name = 'HTTPError';
+		this.statusCode = statusCode;
+		this.statusMessage = statusMessage;
+		this.headers = headers;
+	}
+};
+
+module.exports.MaxRedirectsError = class extends GotError {
+	constructor(statusCode, redirectUrls, opts) {
+		super('Redirected 10 times. Aborting.', {}, opts);
+		this.name = 'MaxRedirectsError';
+		this.statusCode = statusCode;
+		this.statusMessage = http.STATUS_CODES[this.statusCode];
+		this.redirectUrls = redirectUrls;
+	}
+};
+
+module.exports.UnsupportedProtocolError = class extends GotError {
+	constructor(opts) {
+		super(`Unsupported protocol "${opts.protocol}"`, {}, opts);
+		this.name = 'UnsupportedProtocolError';
+	}
+};
+
+module.exports.CancelError = PCancelable.CancelError;
diff --git a/index.js b/index.js
index 8bf06d5..6c82aa8 100644
--- a/index.js
+++ b/index.js
@@ -25,6 +25,7 @@ const pTimeout = require('p-timeout');
 const pify = require('pify');
 const Buffer = require('safe-buffer').Buffer;
 const pkg = require('./package.json');
+const errors = require('./errors');
 
 const getMethodRedirectCodes = new Set([300, 301, 302, 303, 304, 305, 307, 308]);
 const allMethodRedirectCodes = new Set([300, 303, 307, 308]);
@@ -226,33 +227,35 @@ function requestAsEventEmitter(opts) {
 					total: uploadBodySize
 				});
 
-				req.connection.once('connect', () => {
-					const uploadEventFrequency = 150;
+				if (req.connection) {
+					req.connection.once('connect', () => {
+						const uploadEventFrequency = 150;
 
-					progressInterval = setInterval(() => {
-						const lastUploaded = uploaded;
-						const headersSize = Buffer.byteLength(req._header);
-						uploaded = req.connection.bytesWritten - headersSize;
+						progressInterval = setInterval(() => {
+							const lastUploaded = uploaded;
+							const headersSize = Buffer.byteLength(req._header);
+							uploaded = req.connection.bytesWritten - headersSize;
 
-						// Prevent the known issue of `bytesWritten` being larger than body size
-						if (uploadBodySize && uploaded > uploadBodySize) {
-							uploaded = uploadBodySize;
-						}
+							// Prevent the known issue of `bytesWritten` being larger than body size
+							if (uploadBodySize && uploaded > uploadBodySize) {
+								uploaded = uploadBodySize;
+							}
 
-						// Don't emit events with unchanged progress and
-						// prevent last event from being emitted, because
-						// it's emitted when `response` is emitted
-						if (uploaded === lastUploaded || uploaded === uploadBodySize) {
-							return;
-						}
+							// Don't emit events with unchanged progress and
+							// prevent last event from being emitted, because
+							// it's emitted when `response` is emitted
+							if (uploaded === lastUploaded || uploaded === uploadBodySize) {
+								return;
+							}
 
-						ee.emit('uploadProgress', {
-							percent: uploadBodySize ? uploaded / uploadBodySize : 0,
-							transferred: uploaded,
-							total: uploadBodySize
-						});
-					}, uploadEventFrequency);
-				});
+							ee.emit('uploadProgress', {
+								percent: uploadBodySize ? uploaded / uploadBodySize : 0,
+								transferred: uploaded,
+								total: uploadBodySize
+							});
+						}, uploadEventFrequency);
+					});
+				}
 			});
 
 			if (opts.gotTimeout) {
@@ -443,6 +446,13 @@ function normalizeArguments(url, opts) {
 		throw new TypeError(`Parameter \`url\` must be a string or object, not ${is(url)}`);
 	} else if (is.string(url)) {
 		url = url.replace(/^unix:/, 'http://$&');
+
+		try {
+			decodeURI(url);
+		} catch (err) {
+			throw new Error('Parameter `url` must contain valid UTF-8 character sequences');
+		}
+
 		url = urlParseLax(url);
 		if (url.auth) {
 			throw new Error('Basic authentication must be done with the `auth` option');
@@ -597,89 +607,6 @@ for (const method of methods) {
 	got.stream[method] = (url, opts) => got.stream(url, Object.assign({}, opts, {method}));
 }
 
-class StdError extends Error {
-	constructor(message, error, opts) {
-		super(message);
-		Error.captureStackTrace(this, this.constructor);
-		this.name = 'StdError';
-
-		if (!is.undefined(error.code)) {
-			this.code = error.code;
-		}
-
-		Object.assign(this, {
-			host: opts.host,
-			hostname: opts.hostname,
-			method: opts.method,
-			path: opts.path,
-			protocol: opts.protocol,
-			url: opts.href
-		});
-	}
-}
-
-got.CacheError = class extends StdError {
-	constructor(error, opts) {
-		super(error.message, error, opts);
-		this.name = 'CacheError';
-	}
-};
-
-got.RequestError = class extends StdError {
-	constructor(error, opts) {
-		super(error.message, error, opts);
-		this.name = 'RequestError';
-	}
-};
-
-got.ReadError = class extends StdError {
-	constructor(error, opts) {
-		super(error.message, error, opts);
-		this.name = 'ReadError';
-	}
-};
-
-got.ParseError = class extends StdError {
-	constructor(error, statusCode, opts, data) {
-		super(`${error.message} in "${urlLib.format(opts)}": \n${data.slice(0, 77)}...`, error, opts);
-		this.name = 'ParseError';
-		this.statusCode = statusCode;
-		this.statusMessage = http.STATUS_CODES[this.statusCode];
-	}
-};
-
-got.HTTPError = class extends StdError {
-	constructor(statusCode, statusMessage, headers, opts) {
-		if (statusMessage) {
-			statusMessage = statusMessage.replace(/\r?\n/g, ' ').trim();
-		} else {
-			statusMessage = http.STATUS_CODES[statusCode];
-		}
-		super(`Response code ${statusCode} (${statusMessage})`, {}, opts);
-		this.name = 'HTTPError';
-		this.statusCode = statusCode;
-		this.statusMessage = statusMessage;
-		this.headers = headers;
-	}
-};
-
-got.MaxRedirectsError = class extends StdError {
-	constructor(statusCode, redirectUrls, opts) {
-		super('Redirected 10 times. Aborting.', {}, opts);
-		this.name = 'MaxRedirectsError';
-		this.statusCode = statusCode;
-		this.statusMessage = http.STATUS_CODES[this.statusCode];
-		this.redirectUrls = redirectUrls;
-	}
-};
-
-got.UnsupportedProtocolError = class extends StdError {
-	constructor(opts) {
-		super(`Unsupported protocol "${opts.protocol}"`, {}, opts);
-		this.name = 'UnsupportedProtocolError';
-	}
-};
-
-got.CancelError = PCancelable.CancelError;
+Object.assign(got, errors);
 
 module.exports = got;
diff --git a/package.json b/package.json
index 4be16a2..72d3dfe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "got",
-	"version": "8.0.0",
+	"version": "8.0.1",
 	"description": "Simplified HTTP requests",
 	"license": "MIT",
 	"repository": "sindresorhus/got",
@@ -29,7 +29,8 @@
 		"coveralls": "nyc report --reporter=text-lcov | coveralls"
 	},
 	"files": [
-		"index.js"
+		"index.js",
+		"errors.js"
 	],
 	"keywords": [
 		"http",
@@ -50,7 +51,7 @@
 		"electron"
 	],
 	"dependencies": {
-		"@sindresorhus/is": "^0.6.0",
+		"@sindresorhus/is": "^0.7.0",
 		"cacheable-request": "^2.1.1",
 		"decompress-response": "^3.3.0",
 		"duplexer3": "^0.1.4",
@@ -61,7 +62,7 @@
 		"lowercase-keys": "^1.0.0",
 		"mimic-response": "^1.0.0",
 		"p-cancelable": "^0.3.0",
-		"p-timeout": "^1.2.0",
+		"p-timeout": "^2.0.1",
 		"pify": "^3.0.0",
 		"safe-buffer": "^5.1.1",
 		"timed-out": "^4.0.1",
@@ -69,7 +70,7 @@
 		"url-to-options": "^1.0.1"
 	},
 	"devDependencies": {
-		"ava": "^0.23.0",
+		"ava": "^0.24.0",
 		"coveralls": "^3.0.0",
 		"form-data": "^2.1.1",
 		"get-port": "^3.0.0",
diff --git a/readme.md b/readme.md
index 79fb726..43e6bcc 100644
--- a/readme.md
+++ b/readme.md
@@ -195,7 +195,7 @@ Default: `false`
 Type: `boolean`<br>
 Default: `false`
 
-When used in Electron, Got will use [`electron.net`](https://electron.atom.io/docs/api/net/) instead of the Node.js `http` module. According to the Electron docs, it should be fully compatible, but it's not entirely. See [#315](https://github.com/sindresorhus/got/issues/315).
+When used in Electron, Got will use [`electron.net`](https://electronjs.org/docs/api/net/) instead of the Node.js `http` module. According to the Electron docs, it should be fully compatible, but it's not entirely. See [#315](https://github.com/sindresorhus/got/issues/315).
 
 
 #### Streams
@@ -339,6 +339,8 @@ The promise returned by Got has a [`.cancel()`](https://github.com/sindresorhus/
 <a name="cache-adapters"></a>
 ## Cache
 
+Got implements [RFC 7234](http://httpwg.org/specs/rfc7234.html) compliant HTTP caching which works out of the box in memory or is easily pluggable with a wide range of storage adapters. Fresh cache entries are served directly from cache and stale cache entries are revalidated with `If-None-Match`/`If-Modified-Since` headers. You can read more about the underlying cache behaviour in the `cacheable-request` [documentation](https://github.com/lukechilds/cacheable-request).
+
 You can use the JavaScript `Map` type as an in memory cache:
 
 ```js
@@ -567,10 +569,11 @@ Bear in mind, if you send an `if-modified-since` header and receive a `304 Not M
 
 ## Related
 
-- [gh-got](https://github.com/sindresorhus/gh-got) - Convenience wrapper for interacting with the GitHub API
-- [gl-got](https://github.com/singapore/gl-got) - Convenience wrapper for interacting with the GitLab API
-- [travis-got](https://github.com/samverschueren/travis-got) - Convenience wrapper for interacting with the Travis API
-- [graphql-got](https://github.com/kevva/graphql-got) - Convenience wrapper for got to interact with GraphQL
+- [gh-got](https://github.com/sindresorhus/gh-got) - Got convenience wrapper to interact with the GitHub API
+- [gl-got](https://github.com/singapore/gl-got) - Got convenience wrapper to interact with the GitLab API
+- [travis-got](https://github.com/samverschueren/travis-got) - Got convenience wrapper to interact with the Travis API
+- [graphql-got](https://github.com/kevva/graphql-got) - Got convenience wrapper to interact with GraphQL
+- [GotQL](https://github.com/khaosdoctor/gotql) - Got convenience wrapper to interact with GraphQL using JSON-parsed queries instead of strings
 
 
 ## Created by


Test patch:
diff --git a/test/arguments.js b/test/arguments.js
index 1038ac3..6546888 100644
--- a/test/arguments.js
+++ b/test/arguments.js
@@ -29,6 +29,11 @@ test('url is required', async t => {
 	t.regex(err.message, /Parameter `url` must be a string or object, not undefined/);
 });
 
+test('url should be utf-8 encoded', async t => {
+	const err = await t.throws(got(`${s.url}/%D2%E0%EB%EB%E8%ED`));
+	t.regex(err.message, /Parameter `url` must contain valid UTF-8 character sequences/);
+});
+
 test('options are optional', async t => {
 	t.is((await got(`${s.url}/test`)).body, '/test');
 });