Skip to content

Commit b80a08f

Browse files
donemersonjumperchen
authored andcommitted
feat: add support for custom HttpClient configuration
1 parent 2f8b0be commit b80a08f

14 files changed

+220
-14
lines changed

lib/socket_io_client.dart

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'package:socket_io_client/src/manager.dart';
2121

2222
export 'package:socket_io_client/src/socket.dart';
2323
export 'package:socket_io_client/src/darty.dart';
24+
export 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
2425

2526
// Protocol version
2627
final protocol = parser.protocol;

lib/src/darty.dart

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Author: jumperchen<[email protected]>
44

55
import 'package:socket_io_client/socket_io_client.dart';
6+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
67
import 'package:socket_io_common/socket_io_common.dart';
78
import 'package:socket_io_common/src/util/event_emitter.dart';
89

@@ -286,6 +287,11 @@ class OptionBuilder {
286287
return this;
287288
}
288289

290+
OptionBuilder setHttpClientAdapter(HttpClientAdapter httpClientAdapter) {
291+
_opts['httpClientAdapter'] = httpClientAdapter;
292+
return this;
293+
}
294+
289295
/// Build the options map.
290296
Map<String, dynamic> build() => _opts;
291297
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import 'dart:html';
2+
import 'http_client_adapter.dart';
3+
4+
class HtmlHttpClientAdapter implements HttpClientAdapter {
5+
@override
6+
Future<WebSocket> connect(String uri, {Map<String, dynamic>? headers}) {
7+
return Future.value(WebSocket(uri));
8+
}
9+
}
10+
11+
HttpClientAdapter makePlatformHttpClientAdapter() {
12+
return HtmlHttpClientAdapter();
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
abstract class HttpClientAdapter {
2+
Future<dynamic> connect(String uri, {Map<String, dynamic>? headers});
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
2+
3+
import 'unknown_http_client_adapter.dart'
4+
if (dart.library.io) 'io_http_client_adapter.dart'
5+
if (dart.library.html) 'html_http_client_adapter.dart';
6+
7+
HttpClientAdapter createPlatformHttpClientAdapter() {
8+
return makePlatformHttpClientAdapter();
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'dart:io';
2+
import 'http_client_adapter.dart';
3+
4+
class IOHttpClientAdapter implements HttpClientAdapter {
5+
final HttpClient _httpClient;
6+
7+
IOHttpClientAdapter([HttpClient? httpClient])
8+
: _httpClient = httpClient ?? HttpClient();
9+
10+
@override
11+
Future<WebSocket> connect(String uri, {Map<String, dynamic>? headers}) {
12+
return WebSocket.connect(
13+
uri,
14+
headers: headers,
15+
customClient: _httpClient,
16+
);
17+
}
18+
}
19+
20+
HttpClientAdapter makePlatformHttpClientAdapter() {
21+
return IOHttpClientAdapter();
22+
}

lib/src/engine/transport/io_websocket_transport.dart

+9-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import 'dart:io';
77
import 'dart:typed_data';
88
import 'package:logging/logging.dart';
99
import 'package:socket_io_client/src/engine/transport.dart';
10+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
11+
import 'package:socket_io_client/src/engine/transport/http_client_adapter_factory.dart'
12+
as httpClientAdapterFactory;
1013
import 'package:socket_io_common/src/engine/parser/parser.dart';
1114

1215
class IOWebSocketTransport extends Transport {
@@ -20,8 +23,11 @@ class IOWebSocketTransport extends Transport {
2023
Map? perMessageDeflate;
2124
Map<String, dynamic>? extraHeaders;
2225
WebSocket? ws;
23-
24-
IOWebSocketTransport(Map opts) : super(opts) {
26+
HttpClientAdapter httpClientAdapter;
27+
IOWebSocketTransport(Map opts)
28+
: httpClientAdapter = opts['httpClientAdapter'] ??
29+
httpClientAdapterFactory.createPlatformHttpClientAdapter(),
30+
super(opts) {
2531
var forceBase64 = opts['forceBase64'] ?? false;
2632
supportsBinary = !forceBase64;
2733
perMessageDeflate = opts['perMessageDeflate'];
@@ -32,21 +38,11 @@ class IOWebSocketTransport extends Transport {
3238
@override
3339
void doOpen() async {
3440
var uri = this.uri();
35-
var protocols = this.protocols;
36-
3741
try {
38-
ws = await WebSocket.connect(uri,
39-
protocols: protocols, headers: extraHeaders);
42+
ws = await httpClientAdapter.connect(uri, headers: extraHeaders);
4043
} catch (err) {
4144
return emit('error', err);
4245
}
43-
44-
// if (this.ws?.binaryType == null) {
45-
// this.supportsBinary = false;
46-
// }
47-
//
48-
// this.ws?.binaryType = 'arraybuffer';
49-
5046
addEventListeners();
5147
}
5248

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
2+
3+
HttpClientAdapter makePlatformHttpClientAdapter() {
4+
throw UnimplementedError(
5+
'createPlatformHttpClientAdapter() is not implemented for this platform.');
6+
}

lib/src/manager.dart

+13-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import 'package:socket_io_client/src/on.dart';
1111
import 'package:socket_io_client/src/socket.dart';
1212
import 'package:socket_io_client/src/engine/socket.dart' as engine_socket;
1313
import 'package:socket_io_client/src/on.dart' as util;
14+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
15+
import 'package:socket_io_client/src/engine/transport/http_client_adapter_factory.dart'
16+
show createPlatformHttpClientAdapter;
1417

1518
final Logger _logger = Logger('socket_io_client:Manager');
1619

@@ -26,6 +29,7 @@ class Manager extends EventEmitter {
2629
Map<String, Socket> nsps = {};
2730
List subs = [];
2831
late Map? options;
32+
final HttpClientAdapter _httpClientAdapter;
2933

3034
///
3135
/// Sets the `reconnection` config.
@@ -74,8 +78,16 @@ class Manager extends EventEmitter {
7478
late bool autoConnect;
7579
bool? skipReconnect;
7680

77-
Manager({uri, Map? options}) {
81+
Manager({uri, Map? options})
82+
: _httpClientAdapter = createPlatformHttpClientAdapter(),
83+
super() {
7884
options = options ?? <dynamic, dynamic>{};
85+
options['transportOptions'] = {
86+
'websocket': {
87+
'httpClientAdapter': options['httpClientAdapter'] ?? _httpClientAdapter
88+
},
89+
...?options['transportOptions'],
90+
};
7991

8092
options['path'] ??= '/socket.io';
8193
// ignore: prefer_initializing_formals

pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dev_dependencies:
1818
test: ">=1.3.0 <2.0.0"
1919
build_runner: any
2020
build_web_compilers: any
21+
mocktail: ^1.0.4
2122
# socket_io: any // support Socket.io v2.* only
2223

2324

test/http_client_adapter_test.dart

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'package:test/test.dart';
2+
import 'package:mocktail/mocktail.dart';
3+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
4+
import 'package:socket_io_client/src/engine/transport/http_client_adapter_factory.dart'
5+
show createPlatformHttpClientAdapter;
6+
7+
class MockHttpClientAdapter extends Mock implements HttpClientAdapter {}
8+
9+
void main() {
10+
group('HttpClientAdapter', () {
11+
test('Should create the correct platform-specific adapter', () {
12+
final adapter = createPlatformHttpClientAdapter();
13+
expect(adapter, isA<HttpClientAdapter>());
14+
});
15+
16+
test('Should call connect on the adapter with correct parameters',
17+
() async {
18+
final mockAdapter = MockHttpClientAdapter();
19+
const uri = 'wss://example.com/socket.io/';
20+
final headers = {'Authorization': 'Bearer token'};
21+
22+
when(() => mockAdapter.connect(uri, headers: headers))
23+
.thenAnswer((_) async => 'Mock WebSocket Connection');
24+
25+
final result = await mockAdapter.connect(uri, headers: headers);
26+
27+
expect(result, equals('Mock WebSocket Connection'));
28+
verify(() => mockAdapter.connect(uri, headers: headers)).called(1);
29+
});
30+
});
31+
}

test/io_websocket_transport_test.dart

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'package:test/test.dart';
2+
import 'package:mocktail/mocktail.dart';
3+
import 'package:socket_io_client/src/engine/transport/io_websocket_transport.dart';
4+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
5+
6+
class MockHttpClientAdapter extends Mock implements HttpClientAdapter {}
7+
8+
class MockWebSocket extends Mock {}
9+
10+
void main() {
11+
group('IOWebSocketTransport', () {
12+
test('Should use the custom HttpClientAdapter if provided', () async {
13+
final mockAdapter = MockHttpClientAdapter();
14+
final options = {
15+
'httpClientAdapter': mockAdapter,
16+
'secure': true,
17+
'hostname': 'example.com',
18+
'port': 443,
19+
'path': '/socket.io/',
20+
'timestampRequests': true,
21+
'timestampParam': 't',
22+
};
23+
24+
final transport = IOWebSocketTransport(options);
25+
26+
when(() => mockAdapter.connect(any(), headers: any(named: 'headers')))
27+
.thenAnswer((_) async => MockWebSocket());
28+
29+
transport.doOpen();
30+
31+
verify(() => mockAdapter.connect(any(), headers: any(named: 'headers')))
32+
.called(1);
33+
});
34+
35+
test('Should generate the URI correctly', () {
36+
final options = {
37+
'secure': true,
38+
'hostname': 'localhost',
39+
'port': 3000,
40+
'path': '/socket.io/',
41+
'timestampRequests': true,
42+
'timestampParam': 't',
43+
};
44+
45+
final transport = IOWebSocketTransport(options);
46+
final uri = transport.uri();
47+
48+
expect(uri, startsWith('wss://localhost:3000/socket.io/'));
49+
expect(uri, contains('t='));
50+
});
51+
});
52+
}

test/manager_test.dart

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:test/test.dart';
2+
import 'package:mocktail/mocktail.dart';
3+
import 'package:socket_io_client/src/manager.dart';
4+
import 'package:socket_io_client/src/engine/transport/http_client_adapter.dart';
5+
6+
class MockHttpClientAdapter extends Mock implements HttpClientAdapter {}
7+
8+
void main() {
9+
group('Manager', () {
10+
test('Should pass the custom HttpClientAdapter to transport options', () {
11+
final mockAdapter = MockHttpClientAdapter();
12+
final manager = Manager(
13+
uri: 'http://localhost:3000',
14+
options: {'httpClientAdapter': mockAdapter},
15+
);
16+
17+
final transportOptions = manager.options?['transportOptions'];
18+
expect(transportOptions, isNotNull);
19+
expect(transportOptions['websocket']['httpClientAdapter'],
20+
equals(mockAdapter));
21+
});
22+
23+
test('Should use the default HttpClientAdapter if none is provided', () {
24+
final manager = Manager(uri: 'http://localhost:3000');
25+
26+
final transportOptions = manager.options?['transportOptions'];
27+
expect(transportOptions, isNotNull);
28+
expect(transportOptions['websocket']['httpClientAdapter'], isNotNull);
29+
expect(transportOptions['websocket']['httpClientAdapter'],
30+
isA<HttpClientAdapter>());
31+
});
32+
});
33+
}

test/option_builder_test.dart

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:socket_io_client/src/engine/transport/http_client_adapter_factory.dart';
2+
import 'package:test/test.dart';
3+
import 'package:socket_io_client/src/darty.dart';
4+
5+
void main() {
6+
group('OptionBuilder', () {
7+
test('Should configure the custom HttpClientAdapter', () {
8+
final customHttpClientAdapter = createPlatformHttpClientAdapter();
9+
final options =
10+
OptionBuilder().setHttpClientAdapter(customHttpClientAdapter).build();
11+
12+
expect(options['httpClientAdapter'], equals(customHttpClientAdapter));
13+
});
14+
15+
test('Should build options correctly without HttpClientAdapter', () {
16+
final options = OptionBuilder().build();
17+
18+
expect(options.containsKey('httpClientAdapter'), isFalse);
19+
});
20+
});
21+
}

0 commit comments

Comments
 (0)