connectSocket function
Connects to a media socket with the specified options. Validates the API key or token and initiates a socket connection.
Throws an exception if inputs are invalid or if connection fails.
Example usage:
final options = ConnectSocketOptions(
apiUserName: "user123",
apiKey: "yourApiKeyHere",
link: "https://socketlink.com",
);
try {
final socket = await connectSocket(options);
print("Connected to socket with ID: ${socket.id}");
} catch (error) {
print("Failed to connect to socket: $error");
}
Implementation
Future<io.Socket> connectSocket(ConnectSocketOptions options) async {
// Input validation
if (options.apiUserName.isEmpty) throw Exception('API username required.');
if ((options.apiKey?.isEmpty ?? true) &&
(options.apiToken?.isEmpty ?? true)) {
throw Exception('API key or token required.');
}
if (options.link.isEmpty) throw Exception('Socket link required.');
// Validate API key or token format
bool useKey = false;
try {
if (options.apiKey?.length == 64 &&
await validateApiKeyToken(options.apiKey!)) {
useKey = true;
} else if (options.apiToken?.length == 64 &&
await validateApiKeyToken(options.apiToken!)) {
useKey = false;
} else {
throw Exception('Invalid API key or token format.');
}
} catch (error) {
throw Exception('Invalid API key or token.');
}
// Configure socket options based on whether apiKey or apiToken is used
final query = useKey
? {'apiUserName': options.apiUserName, 'apiKey': options.apiKey}
: {'apiUserName': options.apiUserName, 'apiToken': options.apiToken};
final socket = io.io('${options.link}/media', {
'transports': ['websocket'],
'query': query,
'autoConnect': false,
});
final completer = Completer<io.Socket>();
Timer? admissionTimer;
var settled = false;
late void Function(dynamic) handleConnectionSuccess;
late void Function(dynamic) handleConnectError;
late void Function(dynamic) handleSocketError;
late void Function(dynamic) handleDisconnectBeforeReady;
void cleanupAdmissionListeners() {
admissionTimer?.cancel();
socket.off('connection-success', handleConnectionSuccess);
socket.off('connect_error', handleConnectError);
socket.off('error', handleSocketError);
socket.off('disconnect', handleDisconnectBeforeReady);
}
void finishWithError(String stage) {
if (settled) return;
settled = true;
cleanupAdmissionListeners();
socket.disconnect();
completer.completeError(
Exception('Media socket admission failed at $stage.'),
);
}
// A raw Socket.IO connection is only transport establishment. The media
// server emits connection-success after its credential admission finishes;
// do not expose the socket to callers before that application-level proof.
handleConnectionSuccess = (_) {
if (settled) return;
settled = true;
cleanupAdmissionListeners();
completer.complete(socket);
};
// Handle connection error
handleConnectError = (_) => finishWithError('transport_connect_error');
handleSocketError =
(_) => finishWithError('socket_error_before_authorization');
handleDisconnectBeforeReady =
(_) => finishWithError('disconnected_before_authorization');
socket.on('connection-success', handleConnectionSuccess);
socket.on('connect_error', handleConnectError);
socket.on('error', handleSocketError);
socket.on('disconnect', handleDisconnectBeforeReady);
admissionTimer = Timer(
const Duration(seconds: 12),
() => finishWithError('application_authorization_timeout'),
);
socket.connect();
return completer.future;
}