diff --git a/pkgs/cronet_http/.idea/.gitignore b/pkgs/cronet_http/.idea/.gitignore
new file mode 100644
index 0000000000..26d33521af
--- /dev/null
+++ b/pkgs/cronet_http/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/pkgs/cronet_http/.idea/vcs.xml b/pkgs/cronet_http/.idea/vcs.xml
new file mode 100644
index 0000000000..b2bdec2d71
--- /dev/null
+++ b/pkgs/cronet_http/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pkgs/cupertino_http/example/integration_test/websocket_conformance_test.dart b/pkgs/cupertino_http/example/integration_test/websocket_conformance_test.dart
new file mode 100644
index 0000000000..45535395a8
--- /dev/null
+++ b/pkgs/cupertino_http/example/integration_test/websocket_conformance_test.dart
@@ -0,0 +1,6 @@
+import 'package:cupertino_http/cupertino_http.dart';
+import 'package:web_socket_conformance_tests/web_socket_conformance_tests.dart';
+
+void main() {
+ testAll((uri, {protocols}) => CupertinoWebSocket.connect(uri));
+}
diff --git a/pkgs/cupertino_http/example/pubspec.yaml b/pkgs/cupertino_http/example/pubspec.yaml
index 026cb8a39a..a1a92f7dd6 100644
--- a/pkgs/cupertino_http/example/pubspec.yaml
+++ b/pkgs/cupertino_http/example/pubspec.yaml
@@ -31,6 +31,8 @@ dev_dependencies:
integration_test:
sdk: flutter
test: ^1.21.1
+ web_socket_conformance_tests:
+ path: ../../web_socket_conformance_tests
flutter:
uses-material-design: true
diff --git a/pkgs/cupertino_http/lib/cupertino_http.dart b/pkgs/cupertino_http/lib/cupertino_http.dart
index 243ac81436..6bbf729c2e 100644
--- a/pkgs/cupertino_http/lib/cupertino_http.dart
+++ b/pkgs/cupertino_http/lib/cupertino_http.dart
@@ -88,3 +88,4 @@ import 'src/cupertino_client.dart';
export 'src/cupertino_api.dart';
export 'src/cupertino_client.dart';
+export 'src/websocket.dart';
diff --git a/pkgs/cupertino_http/lib/src/websocket.dart b/pkgs/cupertino_http/lib/src/websocket.dart
new file mode 100644
index 0000000000..2674badf64
--- /dev/null
+++ b/pkgs/cupertino_http/lib/src/websocket.dart
@@ -0,0 +1,167 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:cupertino_http/cupertino_http.dart';
+import 'package:websocket/websocket.dart';
+
+class CupertinoWebSocketException extends XXXWebSocketException {
+ CupertinoWebSocketException([super.message = '']);
+
+ factory CupertinoWebSocketException.fromError(Error e) =>
+ CupertinoWebSocketException(e.toString());
+}
+
+class CupertinoWebSocket implements XXXWebSocket {
+ static Future connect(Uri uri) async {
+ final readyCompleter = Completer();
+ late CupertinoWebSocket webSocket;
+
+ final session = URLSession.sessionWithConfiguration(
+ URLSessionConfiguration.defaultSessionConfiguration(),
+ onComplete: (session, task, error) {
+ print('onComplete:');
+ if (!readyCompleter.isCompleted) {
+ if (error != null) {
+ readyCompleter
+ .completeError(CupertinoWebSocketException.fromError(error));
+ } else {
+ readyCompleter.complete();
+ }
+ } else {
+ webSocket._closed(1006, Data.fromList('abnormal close'.codeUnits));
+ }
+ }, onWebSocketTaskOpened: (session, task, protocol) {
+ print('onWebSocketTaskOpened:');
+// _protocol = protocol;
+ readyCompleter.complete();
+ }, onWebSocketTaskClosed: (session, task, closeCode, reason) {
+ print('onWebSocketTaskClosed: $closeCode');
+ webSocket._closed(closeCode, reason);
+ });
+ print(uri);
+ final task = session.webSocketTaskWithRequest(URLRequest.fromUrl(uri))
+ ..resume();
+ await readyCompleter.future;
+ return webSocket = CupertinoWebSocket._(task);
+ }
+
+ final URLSessionWebSocketTask _task;
+ final _events = StreamController();
+
+ void handleMessage(URLSessionWebSocketMessage value) {
+ print('handleMessage: $value');
+ late WebSocketEvent v;
+ switch (value.type) {
+ case URLSessionWebSocketMessageType.urlSessionWebSocketMessageTypeString:
+ v = TextDataReceived(value.string!);
+ break;
+ case URLSessionWebSocketMessageType.urlSessionWebSocketMessageTypeData:
+ v = BinaryDataReceived(value.data!.bytes);
+ break;
+ }
+ _events.add(v);
+ scheduleReceive();
+ }
+
+ void scheduleReceive() {
+// print('scheduleReceive');
+ _task.receiveMessage().then(handleMessage, onError: _closeWithError);
+ }
+
+ CupertinoWebSocket._(this._task) {
+ scheduleReceive();
+ }
+
+ void _closeWithError(Object e) {
+ print('closedWithError: $e');
+ if (e is Error) {
+ if (e.domain == 'NSPOSIXErrorDomain' && e.code == 57) {
+ // Socket is not connected.
+ // onWebSocketTaskClosed/onComplete will be invoked and may indicate a
+ // close code.
+ return;
+ }
+ var (int code, String? reason) = switch ([e.domain, e.code]) {
+ ['NSPOSIXErrorDomain', 100] => (1002, e.localizedDescription),
+ _ => (1006, e.localizedDescription)
+ };
+ _task.cancel();
+ _closed(code, reason == null ? null : Data.fromList(reason.codeUnits));
+ } else {
+ throw UnsupportedError('');
+ }
+ }
+
+ void _closed(int? closeCode, Data? reason) {
+ print('closing with $closeCode');
+ if (!_events.isClosed) {
+ final closeReason = reason == null ? '' : utf8.decode(reason.bytes);
+
+ _events
+ ..add(CloseReceived(closeCode, closeReason))
+ ..close();
+ }
+ }
+
+ @override
+ void sendBytes(Uint8List b) {
+ if (_events.isClosed) {
+ throw StateError('WebSocket is closed');
+ }
+ _task
+ .sendMessage(URLSessionWebSocketMessage.fromData(Data.fromList(b)))
+ .then((_) => _, onError: _closeWithError);
+ }
+
+ @override
+ void sendText(String s) {
+ if (_events.isClosed) {
+ throw StateError('WebSocket is closed');
+ }
+ _task
+ .sendMessage(URLSessionWebSocketMessage.fromString(s))
+ .then((_) => _, onError: _closeWithError);
+ }
+
+ @override
+ Future close([int? code, String? reason]) async {
+ if (_events.isClosed) {
+ throw XXXWebSocketConnectionClosed();
+ }
+
+ if (code != null) {
+ RangeError.checkValueInInterval(code, 3000, 4999, 'code');
+ }
+ if (reason != null && utf8.encode(reason).length > 123) {
+ throw ArgumentError.value(reason, 'reason',
+ 'reason must be <= 123 bytes long when encoded as UTF-8');
+ }
+
+ if (!_events.isClosed) {
+ unawaited(_events.close());
+
+ // XXX Wait until all pending writes are done.
+ print('close($code, $reason)');
+ if (code != null) {
+ reason = reason ?? '';
+ _task.cancelWithCloseCode(code, Data.fromList(reason.codeUnits));
+ } else {
+ _task.cancel();
+ }
+ }
+ }
+
+ @override
+ Stream get events => _events.stream;
+}
+
+/*
+ test('with code and reason', () async {
+ final channel = await channelFactory(uri);
+
+ channel.addString('Please close');
+ expect(await channel.events.toList(),
+ [Closed(4123, 'server closed the connection')]);
+ });
+*/
diff --git a/pkgs/cupertino_http/pubspec.yaml b/pkgs/cupertino_http/pubspec.yaml
index 644bf1bf4d..d0c899993d 100644
--- a/pkgs/cupertino_http/pubspec.yaml
+++ b/pkgs/cupertino_http/pubspec.yaml
@@ -15,6 +15,8 @@ dependencies:
flutter:
sdk: flutter
http: ^1.2.0
+ websocket:
+ path: ../websocket
dev_dependencies:
dart_flutter_team_lints: ^2.0.0
diff --git a/pkgs/web_socket_conformance_tests/.gitignore b/pkgs/web_socket_conformance_tests/.gitignore
new file mode 100644
index 0000000000..3cceda5578
--- /dev/null
+++ b/pkgs/web_socket_conformance_tests/.gitignore
@@ -0,0 +1,7 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
+
+# Avoid committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
diff --git a/pkgs/web_socket_conformance_tests/CHANGELOG.md b/pkgs/web_socket_conformance_tests/CHANGELOG.md
new file mode 100644
index 0000000000..effe43c82c
--- /dev/null
+++ b/pkgs/web_socket_conformance_tests/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/web_socket_conformance_tests/README.md b/pkgs/web_socket_conformance_tests/README.md
new file mode 100644
index 0000000000..8b55e735b5
--- /dev/null
+++ b/pkgs/web_socket_conformance_tests/README.md
@@ -0,0 +1,39 @@
+
+
+TODO: Put a short description of the package here that helps potential users
+know whether this package might be useful for them.
+
+## Features
+
+TODO: List what your package can do. Maybe include images, gifs, or videos.
+
+## Getting started
+
+TODO: List prerequisites and provide or point to information on how to
+start using the package.
+
+## Usage
+
+TODO: Include short and useful examples for package users. Add longer examples
+to `/example` folder.
+
+```dart
+const like = 'sample';
+```
+
+## Additional information
+
+TODO: Tell users more about the package: where to find more information, how to
+contribute to the package, how to file issues, what response they can expect
+from the package authors, and more.
diff --git a/pkgs/web_socket_conformance_tests/analysis_options.yaml b/pkgs/web_socket_conformance_tests/analysis_options.yaml
new file mode 100644
index 0000000000..dee8927aaf
--- /dev/null
+++ b/pkgs/web_socket_conformance_tests/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+# rules:
+# - camel_case_types
+
+# analyzer:
+# exclude:
+# - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/web_socket_conformance_tests/bin/generate_server_wrappers.dart b/pkgs/web_socket_conformance_tests/bin/generate_server_wrappers.dart
new file mode 100644
index 0000000000..7168868db2
--- /dev/null
+++ b/pkgs/web_socket_conformance_tests/bin/generate_server_wrappers.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Generates the '*_server_vm.dart' and '*_server_web.dart' support files.
+library;
+
+import 'dart:core';
+import 'dart:io';
+
+import 'package:dart_style/dart_style.dart';
+
+const vm = '''// Generated by generate_server_wrappers.dart. Do not edit.
+
+import 'package:stream_channel/stream_channel.dart';
+
+import '';
+
+/// Starts the redirect test HTTP server in the same process.
+Future> startServer() async {
+ final controller = StreamChannelController