connectToWebSocket static method

Future<WebSocket> connectToWebSocket(
  1. String url, {
  2. Iterable<String>? protocols,
  3. Map<String, dynamic>? headers,
  4. HttpClient? httpClient,
  5. bool? useStandardWebSocket,
})

Custom WebSocket Connection logic.

Implementation

static Future<WebSocket> connectToWebSocket(
  String url, {
  Iterable<String>? protocols,
  Map<String, dynamic>? headers,
  HttpClient? httpClient,
  bool? useStandardWebSocket,
}) async {
  Uri? uri = Uri.parse(url);

  useStandardWebSocket ??= enableStandardWebSocket;

  if (useStandardWebSocket == true && uri.scheme != 'wss') {
    return await awaitWithTimeout(
      WebSocket.connect(url, protocols: protocols, headers: headers),
      60000,
      onSuccessAfterTimeout: (WebSocket socket) {
        socket.close();
      },
    );
  }

  if (uri.scheme != 'ws' && uri.scheme != 'wss') {
    throw WebSocketException("Unsupported URL scheme '${uri.scheme}'");
  }

  var random = Random();
  // Generate 16 random bytes.
  var nonceData = Uint8List(16);
  for (var i = 0; i < 16; i++) {
    nonceData[i] = random.nextInt(256);
  }
  var nonce = base64.encode(nonceData);

  var port = uri.port;
  if (port == 0) {
    port = uri.scheme == 'wss' ? 443 : 80;
  }

  uri = Uri(
    scheme: uri.scheme == 'wss' ? 'https' : 'http',
    userInfo: uri.userInfo,
    host: uri.host,
    port: port,
    path: uri.path,
    query: uri.query,
  );

  var _client =
      httpClient ??
      (HttpClient()..badCertificateCallback = (a, b, c) => true);

  return _client
      .openUrl('GET', uri)
      .then((HttpClientRequest request) async {
        if (uri?.userInfo != null && uri!.userInfo.isNotEmpty) {
          // If the URL contains user information use that for basic
          // authorization.
          var auth = base64.encode(utf8.encode(uri.userInfo));
          request.headers.set(HttpHeaders.authorizationHeader, 'Basic $auth');
        }
        if (headers != null) {
          headers.forEach(
            (field, dynamic value) => request.headers.add(field, value),
          );
        }
        // Setup the initial handshake.
        request.headers
          ..set(HttpHeaders.connectionHeader, 'Upgrade')
          ..set(HttpHeaders.upgradeHeader, 'websocket')
          ..set('Sec-WebSocket-Key', nonce)
          ..set('Cache-Control', 'no-cache')
          ..set('Sec-WebSocket-Version', '13');
        if (protocols != null) {
          request.headers.add('Sec-WebSocket-Protocol', protocols.toList());
        }
        return request.close();
      })
      .then((response) {
        return response;
      })
      .then((HttpClientResponse response) {
        void error(String message) {
          // Flush data.
          response.detachSocket().then((Socket socket) {
            socket.destroy();
          });
          throw WebSocketException(message);
        }

        if (response.statusCode != HttpStatus.switchingProtocols ||
            response.headers[HttpHeaders.connectionHeader] == null ||
            !response.headers[HttpHeaders.connectionHeader]!.any(
              (value) => value.toLowerCase() == 'upgrade',
            ) ||
            response.headers
                    .value(HttpHeaders.upgradeHeader)!
                    .toLowerCase() !=
                'websocket') {
          error("Connection to '$uri' was not upgraded to websocket");
        }
        var accept = response.headers.value('Sec-WebSocket-Accept');
        if (accept == null) {
          error("Response did not contain a 'Sec-WebSocket-Accept' header");
        }
        var expectedAccept =
            sha1.convert('$nonce$_webSocketGUID'.codeUnits).bytes;
        List<int> receivedAccept = base64.decode(accept!);
        if (expectedAccept.length != receivedAccept.length) {
          error("Response header 'Sec-WebSocket-Accept' is the wrong length");
        }
        for (var i = 0; i < expectedAccept.length; i++) {
          if (expectedAccept[i] != receivedAccept[i]) {
            error("Bad response 'Sec-WebSocket-Accept' header");
          }
        }
        var protocol = response.headers.value('Sec-WebSocket-Protocol');
        return response.detachSocket().then((socket) {
          socket.setOption(SocketOption.tcpNoDelay, _tcpNoDelay);
          return WebSocket.fromUpgradedSocket(
            socket,
            protocol: protocol,
            serverSide: false,
          );
        });
      })
      .timeout(
        Duration(minutes: 1),
        onTimeout: () {
          _client.close(force: true);
          throw WebSocketException('timeout');
        },
      );
}