-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: added WebSocket support in AtLookup #709
Changes from 28 commits
b586311
caa7591
ffc02f5
63fc299
df4658d
ab88f5e
d88a139
417234c
0eca375
b78716a
3f453af
0452d85
a72cf1a
a7c8e64
aee94bb
a67171b
49b840b
4f23462
95fc90c
a635041
65f6141
61e2913
b4eef12
ab72ec6
be8058b
b32009b
9ea9415
5dc5a47
3e05187
8e5aaee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,25 +5,38 @@ import 'dart:convert'; | |
import 'dart:io'; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:at_chops/at_chops.dart'; | ||
import 'package:at_commons/at_builders.dart'; | ||
import 'package:at_commons/at_commons.dart'; | ||
import 'package:at_lookup/at_lookup.dart'; | ||
import 'package:at_lookup/src/connection/at_connection.dart'; | ||
import 'package:at_lookup/src/connection/outbound_message_listener.dart'; | ||
import 'package:at_utils/at_logger.dart'; | ||
import 'package:crypto/crypto.dart'; | ||
import 'package:crypton/crypton.dart'; | ||
import 'package:mutex/mutex.dart'; | ||
import 'package:at_chops/at_chops.dart'; | ||
|
||
class AtLookupImpl implements AtLookUp { | ||
final logger = AtSignLogger('AtLookup'); | ||
|
||
/// Listener for reading verb responses from the remote server | ||
late OutboundMessageListener messageListener; | ||
|
||
OutboundConnection? _connection; | ||
AtConnection? | ||
_connection; // Single variable for both socket and WebSocket connections | ||
|
||
OutboundConnection? get connection => _connection; | ||
AtConnection? get connection => _connection; | ||
|
||
/// Setters or methods to initialize the connection | ||
void setSocketConnection(OutboundConnection socketConnection) { | ||
_connection = socketConnection; | ||
} | ||
|
||
void setWebSocketConnection(OutboundWebSocketConnection webSocketConnection) { | ||
_connection = webSocketConnection; | ||
} | ||
|
||
late AtLookupOutboundConnectionFactory atOutboundConnectionFactory; | ||
|
||
@override | ||
late SecondaryAddressFinder secondaryAddressFinder; | ||
|
@@ -44,12 +57,6 @@ class AtLookupImpl implements AtLookUp { | |
|
||
late SecureSocketConfig _secureSocketConfig; | ||
|
||
late final AtLookupSecureSocketFactory socketFactory; | ||
|
||
late final AtLookupSecureSocketListenerFactory socketListenerFactory; | ||
|
||
late AtLookupOutboundConnectionFactory outboundConnectionFactory; | ||
|
||
/// Represents the client configurations. | ||
late Map<String, dynamic> _clientConfig; | ||
|
||
|
@@ -61,9 +68,10 @@ class AtLookupImpl implements AtLookUp { | |
SecondaryAddressFinder? secondaryAddressFinder, | ||
SecureSocketConfig? secureSocketConfig, | ||
Map<String, dynamic>? clientConfig, | ||
AtLookupSecureSocketFactory? secureSocketFactory, | ||
AtLookupSecureSocketListenerFactory? socketListenerFactory, | ||
AtLookupOutboundConnectionFactory? outboundConnectionFactory}) { | ||
AtLookupOutboundConnectionFactory? atOutboundConnectionFactory}) { | ||
// Default to secure socket factory | ||
this.atOutboundConnectionFactory = | ||
atOutboundConnectionFactory ?? AtLookupSecureSocketFactory(); | ||
_currentAtSign = atSign; | ||
_rootDomain = rootDomain; | ||
_rootPort = rootPort; | ||
|
@@ -73,11 +81,6 @@ class AtLookupImpl implements AtLookUp { | |
// Stores the client configurations. | ||
// If client configurations are not available, defaults to empty map | ||
_clientConfig = clientConfig ?? {}; | ||
socketFactory = secureSocketFactory ?? AtLookupSecureSocketFactory(); | ||
this.socketListenerFactory = | ||
socketListenerFactory ?? AtLookupSecureSocketListenerFactory(); | ||
this.outboundConnectionFactory = | ||
outboundConnectionFactory ?? AtLookupOutboundConnectionFactory(); | ||
} | ||
|
||
@Deprecated('use CacheableSecondaryAddressFinder') | ||
|
@@ -246,16 +249,18 @@ class AtLookupImpl implements AtLookUp { | |
await _connection!.close(); | ||
} | ||
logger.info('Creating new connection'); | ||
//1. find secondary url for atsign from lookup library | ||
|
||
// 1. Find secondary URL for the atsign from the lookup library | ||
SecondaryAddress secondaryAddress = | ||
await secondaryAddressFinder.findSecondary(_currentAtSign); | ||
var host = secondaryAddress.host; | ||
var port = secondaryAddress.port; | ||
//2. create a connection to secondary server | ||
await createOutBoundConnection( | ||
host, port.toString(), _currentAtSign, _secureSocketConfig); | ||
//3. listen to server response | ||
messageListener = socketListenerFactory.createListener(_connection!); | ||
|
||
// 2. Create a connection to the secondary server | ||
await createOutboundConnection( | ||
host, port.toString(), _secureSocketConfig); | ||
|
||
// 3. Listen to server response | ||
messageListener.listen(); | ||
logger.info('New connection created OK'); | ||
} | ||
|
@@ -524,31 +529,40 @@ class AtLookupImpl implements AtLookUp { | |
await createConnection(); | ||
try { | ||
await _cramAuthenticationMutex.acquire(); | ||
|
||
if (!_connection!.getMetaData()!.isAuthenticated) { | ||
// Use the connection and message listener dynamically | ||
await _sendCommand((FromVerbBuilder() | ||
..atSign = _currentAtSign | ||
..clientConfig = _clientConfig) | ||
.buildCommand()); | ||
|
||
var fromResponse = await messageListener.read( | ||
transientWaitTimeMillis: 4000, maxWaitMilliSeconds: 10000); | ||
logger.info('from result:$fromResponse'); | ||
|
||
if (fromResponse.isEmpty) { | ||
return false; | ||
} | ||
|
||
fromResponse = fromResponse.trim().replaceAll('data:', ''); | ||
|
||
var digestInput = '$secret$fromResponse'; | ||
var bytes = utf8.encode(digestInput); | ||
var digest = sha512.convert(bytes); | ||
|
||
await _sendCommand('cram:$digest\n'); | ||
var cramResponse = await messageListener.read( | ||
transientWaitTimeMillis: 4000, maxWaitMilliSeconds: 10000); | ||
|
||
if (cramResponse == 'data:success') { | ||
logger.info('auth success'); | ||
_connection!.getMetaData()!.isAuthenticated = true; | ||
} else { | ||
throw UnAuthenticatedException('Auth failed'); | ||
} | ||
} | ||
|
||
return _connection!.getMetaData()!.isAuthenticated; | ||
} finally { | ||
_cramAuthenticationMutex.release(); | ||
|
@@ -628,19 +642,28 @@ class AtLookupImpl implements AtLookUp { | |
!(_connection!.getMetaData()!.isAuthenticated); | ||
} | ||
|
||
Future<bool> createOutBoundConnection(String host, String port, | ||
String toAtSign, SecureSocketConfig secureSocketConfig) async { | ||
Future<bool> createOutboundConnection( | ||
String host, String port, SecureSocketConfig secureSocketConfig) async { | ||
try { | ||
SecureSocket secureSocket = | ||
await socketFactory.createSocket(host, port, secureSocketConfig); | ||
_connection = | ||
outboundConnectionFactory.createOutboundConnection(secureSocket); | ||
// Create the socket connection using the factory | ||
final underlying = await atOutboundConnectionFactory.createUnderlying( | ||
host, port, secureSocketConfig); | ||
|
||
// Create the outbound connection and listener using the factory's methods | ||
final outboundConnection = | ||
atOutboundConnectionFactory.outBoundConnectionFactory(underlying); | ||
messageListener = atOutboundConnectionFactory | ||
.atLookupSocketListenerFactory(outboundConnection); | ||
|
||
_connection = outboundConnection; | ||
|
||
// Set idle time if applicable | ||
if (outboundConnectionTimeout != null) { | ||
_connection!.setIdleTime(outboundConnectionTimeout); | ||
outboundConnection.setIdleTime(outboundConnectionTimeout); | ||
} | ||
} on SocketException { | ||
throw SecondaryConnectException( | ||
'unable to connect to secondary $toAtSign on $host:$port'); | ||
'Unable to connect to secondary $_currentAtSign on $host:$port'); | ||
} | ||
return true; | ||
} | ||
|
@@ -661,7 +684,7 @@ class AtLookupImpl implements AtLookUp { | |
Future<void> _sendCommand(String command) async { | ||
await createConnection(); | ||
logger.finer('SENDING: $command'); | ||
await _connection!.write(command); | ||
_connection!.write(command); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. await is removed here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think await is needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I verified the code and "write" operation on the socket does not return a future. Hence suggested to remove await. |
||
} | ||
|
||
@override | ||
|
@@ -682,23 +705,3 @@ class AtLookupImpl implements AtLookUp { | |
@override | ||
String? enrollmentId; | ||
} | ||
|
||
class AtLookupSecureSocketFactory { | ||
Future<SecureSocket> createSocket( | ||
String host, String port, SecureSocketConfig socketConfig) async { | ||
return await SecureSocketUtil.createSecureSocket(host, port, socketConfig); | ||
} | ||
} | ||
|
||
class AtLookupSecureSocketListenerFactory { | ||
OutboundMessageListener createListener( | ||
OutboundConnection outboundConnection) { | ||
return OutboundMessageListener(outboundConnection); | ||
} | ||
} | ||
|
||
class AtLookupOutboundConnectionFactory { | ||
OutboundConnection createOutboundConnection(SecureSocket secureSocket) { | ||
return OutboundConnectionImpl(secureSocket); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:at_commons/at_commons.dart'; | ||
import 'package:at_lookup/at_lookup.dart'; | ||
import 'package:at_lookup/src/connection/outbound_websocket_connection_impl.dart'; | ||
import 'outbound_message_listener.dart'; | ||
|
||
/// This factory is responsible for creating the underlying connection, | ||
/// an outbound connection wrapper, and the message listener for a | ||
/// specific type of connection (e.g., `SecureSocket` or `WebSocket`). | ||
abstract class AtLookupOutboundConnectionFactory<T, U> { | ||
/// Creates the underlying connection of type [T]. | ||
/// | ||
/// Takes [host], [port], and [secureSocketConfig] as parameters to establish | ||
/// a secure connection based on the provided configuration. | ||
Future<T> createUnderlying( | ||
String host, String port, SecureSocketConfig secureSocketConfig); | ||
|
||
/// Wraps the underlying connection of type [T] into an outbound connection [U]. | ||
U outBoundConnectionFactory(T underlying); | ||
|
||
/// Creates an [OutboundMessageListener] to manage messages for the given [U] connection. | ||
OutboundMessageListener atLookupSocketListenerFactory(U connection); | ||
} | ||
|
||
/// Factory class to create a secure outbound connection over [SecureSocket]. | ||
/// | ||
/// This class handles the creation of a secure socket-based connection, | ||
/// its outbound connection wrapper, and the corresponding message listener. | ||
class AtLookupSecureSocketFactory extends AtLookupOutboundConnectionFactory< | ||
SecureSocket, OutboundConnection> { | ||
/// Creates a secure socket connection to the specified [host] and [port] | ||
/// using the given [secureSocketConfig]. | ||
/// | ||
/// Returns a [SecureSocket] that establishes a secure connection | ||
@override | ||
Future<SecureSocket> createUnderlying( | ||
String host, String port, SecureSocketConfig secureSocketConfig) async { | ||
return await SecureSocketUtil.createSecureSocket( | ||
host, port, secureSocketConfig); | ||
} | ||
|
||
/// Wraps the [SecureSocket] connection into an [OutboundConnection] instance. | ||
@override | ||
OutboundConnection outBoundConnectionFactory(SecureSocket underlying) { | ||
return OutboundConnectionImpl(underlying); | ||
} | ||
|
||
/// Creates an [OutboundMessageListener] to manage messages for the secure | ||
/// socket-based [OutboundConnection]. | ||
@override | ||
OutboundMessageListener atLookupSocketListenerFactory( | ||
OutboundConnection connection) { | ||
return OutboundMessageListener(connection); | ||
} | ||
} | ||
|
||
/// Factory class to create a WebSocket-based outbound connection. | ||
/// | ||
/// This class handles the creation of a WebSocket connection, | ||
/// its outbound connection wrapper, and the associated message listener. | ||
class AtLookupWebSocketFactory extends AtLookupOutboundConnectionFactory< | ||
WebSocket, OutboundWebSocketConnection> { | ||
/// Creates a WebSocket connection to the specified [host] and [port] | ||
/// using the given [secureSocketConfig]. | ||
@override | ||
Future<WebSocket> createUnderlying( | ||
String host, String port, SecureSocketConfig secureSocketConfig) async { | ||
final socket = await SecureSocketUtil.createSecureSocket( | ||
host, port, secureSocketConfig, | ||
isWebSocket: true); | ||
return socket as WebSocket; | ||
} | ||
|
||
/// Wraps the [WebSocket] connection into an [OutboundWebSocketConnection] instance. | ||
/// | ||
/// This outbound connection manages WebSocket-specific message handling and | ||
/// provides additional methods for WebSocket communication. | ||
@override | ||
OutboundWebSocketConnection outBoundConnectionFactory(WebSocket underlying) { | ||
return OutboundWebsocketConnectionImpl(underlying); | ||
} | ||
|
||
/// Creates an [OutboundMessageListener] to manage messages for the | ||
/// WebSocket-based [OutboundWebSocketConnection]. | ||
@override | ||
OutboundMessageListener atLookupSocketListenerFactory( | ||
OutboundWebSocketConnection connection) { | ||
return OutboundMessageListener(connection); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we have modified SecondaryUrlFinder constructor and exported this class, the change should be treated as breaking and have a major release
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated be8058b