introduce an optional 'onError' parameter (#92)

* introduce an optional  parameter

* address typo

* add some test plumbing
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cebbe9b..1301a48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.2.0
+
+- Introduce an optional `onError` parameter when setting up a [WipConnection].
+  This can be used to report errors from the underlying [WebSocket].
+
 ## 1.1.0
 
 - Have `ChromeConnection.getTabs` return better exceptions where there's a
diff --git a/lib/webkit_inspection_protocol.dart b/lib/webkit_inspection_protocol.dart
index 6f5f7df..251cd1a 100644
--- a/lib/webkit_inspection_protocol.dart
+++ b/lib/webkit_inspection_protocol.dart
@@ -175,8 +175,14 @@
 
   bool get isBackgroundPage => type == 'background_page';
 
-  Future<WipConnection> connect() =>
-      WipConnection.connect(webSocketDebuggerUrl);
+  /// Connect to the debug connection for this tab and return a [WipConnection].
+  ///
+  /// On errors from this stream, the [onError] handler is called with the error
+  /// object and possibly a stack trace. The [onError] callback must be of type
+  /// `void Function(Object error)` or `void Function(Object error, StackTrace)`.
+  Future<WipConnection> connect({Function? onError}) {
+    return WipConnection.connect(webSocketDebuggerUrl, onError: onError);
+  }
 
   @override
   String toString() => url;
@@ -216,23 +222,34 @@
   final _closeController = StreamController<WipConnection>.broadcast();
   final _notificationController = StreamController<WipEvent>.broadcast();
 
-  static Future<WipConnection> connect(String url) {
+  /// Connect to the given url and return a [WipConnection].
+  ///
+  /// On errors from this stream, the [onError] handler is called with the error
+  /// object and possibly a stack trace. The [onError] callback must be of type
+  /// `void Function(Object error)` or `void Function(Object error, StackTrace)`.
+  static Future<WipConnection> connect(String url, {Function? onError}) {
     return WebSocket.connect(url).then((socket) {
-      return WipConnection._(url, socket);
+      return WipConnection._(url, socket, onError: onError);
     });
   }
 
-  WipConnection._(this.url, this._ws) {
-    _ws.listen((data) {
-      var json = jsonDecode(data as String) as Map<String, dynamic>;
+  WipConnection._(this.url, this._ws, {Function? onError}) {
+    void onData(dynamic /*String|List<int>*/ data) {
       _onReceive.add(data);
 
+      var json = jsonDecode(data as String) as Map<String, dynamic>;
       if (json.containsKey('id')) {
         _handleResponse(json);
       } else {
         _handleNotification(json);
       }
-    }, onDone: _handleClose);
+    }
+
+    _ws.listen(
+      onData,
+      onError: onError,
+      onDone: _handleClose,
+    );
   }
 
   Stream<WipConnection> get onClose => _closeController.stream;
@@ -244,8 +261,10 @@
   @override
   String toString() => url;
 
-  Future<WipResponse> sendCommand(String method,
-      [Map<String, dynamic>? params]) {
+  Future<WipResponse> sendCommand(
+    String method, [
+    Map<String, dynamic>? params,
+  ]) {
     var completer = Completer<WipResponse>();
     var json = {'id': _nextId++, 'method': method};
     if (params != null) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 2aa2801..abcacc0 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: webkit_inspection_protocol
-version: 1.1.0
+version: 1.2.0
 description: >
   A client for the Chrome DevTools Protocol (previously called the Webkit
   Inspection Protocol).
@@ -13,7 +13,7 @@
 
 dev_dependencies:
   args: ^2.0.0
-  lints: ^2.0.0
+  lints: '>=1.0.0 <3.0.0'
   shelf_static: ^1.0.0
   shelf_web_socket: ^1.0.0
   shelf: ^1.0.0
diff --git a/test/test_setup.dart b/test/test_setup.dart
index 857e445..afd1d64 100644
--- a/test/test_setup.dart
+++ b/test/test_setup.dart
@@ -13,7 +13,7 @@
 Future<WipConnection>? _wipConnection;
 
 /// Returns a (cached) debugger connection to the first regular tab of
-/// the browser with remote debugger running at 'localhost:9222',
+/// the browser with remote debugger running at 'localhost:9222'.
 Future<WipConnection> get wipConnection {
   _wipConnection ??= () async {
     var debugPort = await _startWebDriver(await _startChromeDriver());
@@ -27,6 +27,21 @@
   return _wipConnection!;
 }
 
+/// Returns a (cached) debugger connection to the first regular tab of
+/// the browser with remote debugger running at 'localhost:9222'.
+Future<WipConnection> createWipConnection({Function? onError}) {
+  _wipConnection ??= () async {
+    var debugPort = await _startWebDriver(await _startChromeDriver());
+    var chrome = ChromeConnection('localhost', debugPort);
+    var tab = (await chrome
+        .getTab((tab) => !tab.isBackgroundPage && !tab.isChromeExtension))!;
+    var connection = await tab.connect(onError: onError);
+    connection.onClose.listen((_) => _wipConnection = null);
+    return connection;
+  }();
+  return _wipConnection!;
+}
+
 Process? _chromeDriver;
 
 /// Starts ChromeDriver and returns the listening port.
@@ -61,18 +76,24 @@
   var capabilities = Capabilities.chrome
     ..addAll({
       Capabilities.chromeOptions: {
-        'args': ['remote-debugging-port=$debugPort', '--headless']
+        'args': ['remote-debugging-port=$debugPort', '--headless'],
       }
     });
 
-  await createDriver(
-      spec: WebDriverSpec.JsonWire,
-      desired: capabilities,
-      uri: Uri.parse('http://127.0.0.1:$chromeDriverPort/wd/hub/'));
+  _webDriver = await createDriver(
+    spec: WebDriverSpec.JsonWire,
+    desired: capabilities,
+    uri: Uri.parse('http://127.0.0.1:$chromeDriverPort/wd/hub/'),
+  );
 
   return debugPort;
 }
 
+void killChromeDriver() {
+  _chromeDriver?.kill();
+  _chromeDriver = null;
+}
+
 /// Returns a port that is probably, but not definitely, not in use.
 ///
 /// This has a built-in race condition: another process may bind this port at
@@ -118,10 +139,12 @@
 
 Future closeConnection() async {
   if (_wipConnection != null) {
-    await _webDriver?.quit(closeSession: true);
+    await _webDriver?.quit(closeSession: true).catchError((e) => null);
     _webDriver = null;
+
     _chromeDriver?.kill();
     _chromeDriver = null;
+
     _wipConnection = null;
   }
 }