temp commit

main_production_audio_video_call
Sikander Saleem 1 month ago
parent 5e93e12e2f
commit 13aa5a1258

@ -56,12 +56,46 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin {
super.dispose();
}
/// See [ChatProvider.buildHubConnection] for why a `:0` port can show up in
/// SignalR websocket errors - it is a `dart:io` artifact (Uri.port == 0 for
/// the `wss` scheme), not a real port. Read the trailing HTTP status instead.
Future<HubConnection> getHubConnection(String token) async {
HubConnection hub;
HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true);
hub = HubConnectionBuilder().withUrl("${URLs.chatHubUrlChat}?access_token=$token", options: httpOp).withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000]).build();
// Encode the JWT properly and also pass it as an Authorization header:
// on mobile signalr_netcore ignores the query token for the websocket
// handshake and relies on `accessTokenFactory`.
final String hubUrl = _urlWithExplicitPort(
Uri.parse(URLs.chatHubUrlChat).replace(queryParameters: <String, String>{
"access_token": token,
}),
);
final HttpConnectionOptions httpOp = HttpConnectionOptions(
skipNegotiation: false,
logMessageContent: true,
accessTokenFactory: () async => token,
requestTimeout: 30000,
);
return HubConnectionBuilder()
.withUrl(hubUrl, options: httpOp)
.withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000])
.build();
}
return hub;
/// Writes the port into the url *textually*.
///
/// Why this is not just `uri.replace(port: 443)`: `Uri` normalises a port away
/// when it equals the scheme default, so `.replace(port: 443)` on an https uri
/// serialises back to `https://host/...` with no port at all. signalr_netcore
/// only does string surgery on this url (`url.replaceFirst('http', 'ws')`), so
/// the port has to be present in the string to survive into the `wss://` form.
/// Without it `Uri.port` is `0` for `ws`/`wss` (dart:io only knows defaults for
/// http/https), which is exactly what gets echoed back as `https://host:0/...`
/// in WebSocketException messages.
static String _urlWithExplicitPort(Uri uri) {
final int port = uri.hasPort ? uri.port : (uri.scheme == 'https' || uri.scheme == 'wss' ? 443 : 80);
final String query = uri.query.isEmpty ? '' : '?${uri.query}';
return '${uri.scheme}://${uri.host}:$port${uri.path}$query';
}
Future<bool> buildHubConnection(String employeeNumber, {HubConnection? hubC}) async {

@ -306,6 +306,21 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
// }
/// Opens the SignalR connection for [conversationID].
///
/// IMPORTANT - about the ":0" port you may see in the logs:
/// `WebSocketException: Connection to 'https://atomsmdev.hmg.com:0/api/DeriChat/hubs/chat?...'
/// was not upgraded to websocket, HTTP status code: 401`
///
/// The `:0` is NOT coming from our url and it is NOT a real port. `dart:io`
/// only knows default ports for `http`/`https`, so `Uri.parse('wss://host/x').port`
/// evaluates to `0`. `_WebSocketImpl.connect()` then rebuilds the uri with
/// `port: uri.port` (= 0) purely to print it / feed `HttpClient.openUrl`, and
/// `HttpClient` maps port 0 back to the scheme default (443). The socket is
/// therefore opened on 443 as expected.
/// => Ignore the `:0`, the real failure is always the `HTTP status code` at the
/// end of that message (usually 401 = expired/missing chat token).
/// [_withExplicitPort] below suppresses the `:0` so the logs stay readable.
Future<void> buildHubConnection(String conversationID) async {
try {
// Dispose existing connection if any
@ -332,7 +347,7 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
// chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
} catch (e) {
if (kDebugMode) {
print('⚠️ Error building SignalR connection: $e');
print('⚠️ Error building SignalR connection: ${_describeHubError(e)}');
}
// Clean up on error
await _disposeConnection();
@ -340,19 +355,85 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin {
}
}
/// Writes the port into the url *textually*.
///
/// Why this is not just `uri.replace(port: 443)`: `Uri` normalises a port away
/// when it equals the scheme default, so `.replace(port: 443)` on an https uri
/// serialises back to `https://host/...` with no port at all. signalr_netcore
/// only does string surgery on this url (`url.replaceFirst('http', 'ws')`), so
/// the port has to be present in the string to survive into the `wss://` form.
/// Without it `Uri.port` is `0` for `ws`/`wss` (dart:io only knows defaults for
/// http/https), which is exactly what gets echoed back as `https://host:0/...`
/// in WebSocketException messages.
static String _urlWithExplicitPort(Uri uri) {
final int port = uri.hasPort ? uri.port : (uri.scheme == 'https' || uri.scheme == 'wss' ? 443 : 80);
final String query = uri.query.isEmpty ? '' : '?${uri.query}';
return '${uri.scheme}://${uri.host}:$port${uri.path}$query';
}
/// Turns SignalR/websocket failures into something actionable.
/// The `:0` in the raw text is noise - what matters is the HTTP status code.
static String _describeHubError(Object e) {
final String raw = e.toString();
final Match? status = RegExp(r'HTTP status code:\s*(\d{3})').firstMatch(raw);
if (status == null) return raw;
final String code = status.group(1)!;
const Map<String, String> hints = <String, String>{
'401': 'chat token missing/expired -> re-run getUserAutoLoginTokenSilent()',
'403': 'token valid but not allowed to join this conversation',
'404': 'hub path is wrong -> check URLs.chatHubUrlChat',
'502': 'gateway/proxy is not forwarding the websocket upgrade',
'504': 'gateway timeout on the websocket upgrade',
};
return 'websocket upgrade rejected with HTTP $code'
'${hints[code] == null ? '' : ' (${hints[code]})'}'
' [ignore any ":0" port in the raw message]\n raw: $raw';
}
Future<HubConnection> getHubConnection() async {
if (kDebugMode) {
print('🔧 Creating new SignalR hub connection...');
}
HubConnection hub;
HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true);
hub = HubConnectionBuilder()
.withUrl("${URLs.chatHubUrlChat}?UserId=${chatLoginResponse!.userId}&source=Desktop&access_token=${chatLoginResponse!.token}", options: httpOp)
.withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000]).build();
final ChatLoginResponse? login = chatLoginResponse;
final String? token = login?.token;
if (token == null || token.isEmpty) {
throw StateError('Cannot open SignalR hub: chat login token is null/empty. Call getUserAutoLoginTokenSilent() first.');
}
// NOTE: build the url with Uri so every query value is properly encoded.
// Never hand-concatenate the JWT - unencoded characters silently break the
// negotiate request and the server answers 401 (which surfaces as the
// confusing "https://host:0/... was not upgraded to websocket" error,
// see comment on [buildHubConnection]).
final Uri hubUri = Uri.parse(URLs.chatHubUrlChat).replace(queryParameters: <String, String>{
"UserId": "${login?.userId}",
"source": "Desktop",
"access_token": token,
});
final String hubUrl = _urlWithExplicitPort(hubUri);
// Send the token as a real `Authorization: Bearer` header too.
// signalr_netcore uses `accessTokenFactory` for BOTH the /negotiate call and
// the websocket handshake on mobile (query strings are only used on web),
// so without this the websocket upgrade request is unauthenticated -> 401.
final HttpConnectionOptions httpOp = HttpConnectionOptions(
skipNegotiation: false,
logMessageContent: true,
accessTokenFactory: () async => token,
requestTimeout: 30000, // package default is only 2000ms
);
final HubConnection hub = HubConnectionBuilder()
.withUrl(hubUrl, options: httpOp)
.withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000])
.build();
if (kDebugMode) {
print('✅ SignalR hub connection created');
print('✅ SignalR hub connection created -> ${_urlWithExplicitPort(hubUri.replace(queryParameters: {
...hubUri.queryParameters,
"access_token": "***",
}))}');
}
return hub;

Loading…
Cancel
Save