Cannot connect to wss with Flutter - android

I wrote a wss server in Nodejs and now I'm trying to connect to such server using Flutter.
Here's the code in NodeJS:
//Dependencies
const WebSocket = require('ws');
const fs = require('fs');
const https = require('https');
//Dependencies
//Server declarations
const server = https.createServer({
key: fs.readFileSync('pathTo/key.pem'),
cert: fs.readFileSync('pathTo/cert.pem')
});
server.listen(xxxx);
const wss = new WebSocket.Server({ server });
//Server declarations
wss.on('connection', function connection(ws)
{
ws.on('message', function incoming(message)
{
console.log('Received: ' + message);
ws.send('echo: ' + message);
});
ws.send('Connected!');
});
Here's the code in Flutter:
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:web_socket_channel/io.dart';
import 'package:connectivity/connectivity.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class MyApp extends StatelessWidget
{
#override
Widget build(BuildContext context)
{
final title = 'LumenApp Prototype';
IOWebSocketChannel channel;
try
{
channel = new IOWebSocketChannel.connect('wss://xxxxxxxx.xxx.xxx:xxxx/');
MyHomePageState.noResponse = false;
}
catch(e)
{
MyHomePageState.noResponse = true;
}
return MaterialApp(
title: title,
theme: ThemeData(
primarySwatch: Colors.blue,
primaryTextTheme: TextTheme(
title: TextStyle(
color: Colors.yellow[600],
),
),
),
home: MyHomePage(
title: title,
channel: channel,
),
);
}
}
The error on Flutter is: WebSocketChannelException: WebSocketChannelException: HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: self signed certificate(handshake.cc:354))
This happens inside this function:
void initPlatformState()
{
widget.channel.stream.listen((message)
{
setState(() { noResponse = false; });
//Handle message...
},
onError: (error)
{
print(error);
if(mounted)
{
setState((){ noResponse = true;});
}
},
onDone: ()
{
if(mounted)
{
setState((){ noResponse = true; });
}
});
}
I used a self-signed certificate server-side made with openssl.
Any idea how to solve this?

If you happen to bumped in this GitHub post, you can follow the temporary fix from this comment:
class MyHttpOverrides extends HttpOverrides{
#override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
void main(){
HttpOverrides.global = new MyHttpOverrides();
runApp(new MyApp());
}
It works on local ip with self signed certificate.
To elaborate, here is the same solution:
Just for the sake of clarity specially for the newcomers to
Flutter/Dart, here is what you need to do in order to enable this
option globally in your project:
In your main.dart file, add or import the following class:
HttpClient createHttpClient(SecurityContext? context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true; } } ```
In your main function, add the following line after function definition:
HttpOverrides.global = MyHttpOverrides();
This
comment was very helpful to pass through this matter, and please
note that...
This should be used while in development mode, do NOT do this when
you want to release to production, the aim of this answer is to make
the development a bit easier for you, for production, you need to fix
your certificate issue and use it properly, look at the other answers
for this as it might be helpful for your case.
Another thing worth mentioning, signed certificates are available for free now (https://letsencrypt.org/).
Also, I think the Flutter team is working to enhance the documentation for better reference regarding this issue. It is being tracked here.

Related

Why my flutter http network calls are slow?

I am developing a flutter application with network activities. To get data, I am connecting to a REST API, this API is fast as it should.
For more information, this API is using AWS API Gateway and AWS Lambda along with other AWS technologies.
Below is my code, connecting to network.
class RoleService with ChangeNotifier {
NavLinks _navLinks = NavLinks();
late List<Role> _roles;
/// Get all Roles
Future<void> getAllRoles(String authToken) async {
try {
var data = await http.get(
Uri.parse("https://api2.example.com/userrel/roles/getall"),
headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"},
);
var jsonData =
convert.json.decode(data.body).cast<Map<String, dynamic>>();
_roles = jsonData.map<Role>((json) => new Role.fromJson(json)).toList();
print(_roles);
} catch (error) {
print(error);
throw error;
}
}
}
You can see the postman performance of the above API call below. For flutter testing, i am using Huawei p30 Lite android phone.
Then, when I execute the same API call in flutter, this is what I get.
Observing the outputs from postman I can see it has cached the DNS Lookup, TCP Handshake and SSL Handshake. postman does this after calling the API base URI for the first time. Then from the 2nd time onwards, the DNS Lookup etc are cached saving lot of time in future API calls to the same base URI.
But in flutter the "Connection established" time is high, even though the time to retrieve data is only few milliseconds.
How can I avoid the connection delays and get the maximum performance? If caching the SSL, DNS Lookup etc is the solution, how can I do that in flutter?
It seems this question is there for many people. So, let me answer my own question.
Can flutter remember the network connection? Yes it can.
Flutter only require one network call to the same API to remember the connection. From the second call onward to the same API, it will use its "cached" memory giving you a big performance boost.
So first remember, this only works if you are calling the same API multiple times. If you are calling different APIs, this will not work. However in many apps, you have an API that built by the API team and you will be calling the same throughput the app.
The solution is to use flutter http.Client. Then share the same http.Client across the calls you make to the same API. You will see only first call takes time for "connection", rest of the calls do not take that time.
An example is available in flutter http pub page. It says ,
If you're making multiple requests to the same server, you can keep
open a persistent connection by using a Client rather than making
one-off requests. If you do this, make sure to close the client when
you're done:
Check below example. It is only for your reference, not the best way of using this.
main.dart
import 'package:flutter/material.dart';
import 'package:network_test/role_service.dart';
import 'package:network_test/user_role_service.dart';
import 'package:network_test/user_service.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var startTime = "";
var endTime = "";
void _network() async {
var client = http.Client();
RoleService _roleService = RoleService();
UserService _userService = UserService();
UserRoleService _userRoleService = UserRoleService();
String authToken = "****";
String uid = "555555";
try {
await _roleService.getAllRoles(authToken, client);
//await _roleService.getAllRoles(authToken, client);
await _userService.getUserByUID(authToken, uid, client);
await _userService.getUserByID(authToken, 27, client);
await _userRoleService.getUserRoleByUser(authToken, 27, client);
} finally {
client.close();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
"Start Time: " + startTime,
style: Theme.of(context).textTheme.headline4,
),
Text(
"End Time: " + endTime,
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _network,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
role_service.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:network_test/role.dart';
import 'dart:convert' as convert;
import 'dart:io';
class RoleService with ChangeNotifier {
late List<Role> _roles;
String link2 = "https://api2.somewhere.com/userrel";
/// Return roles
List<Role> returnRoles() {
return _roles;
}
/// Get all Roles
Future<void> getAllRoles(String authToken, Client client) async {
try {
var data = await client.get(Uri.parse(link2 + "/role/getall"),
headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"});
var jsonData =
convert.json.decode(data.body).cast<Map<String, dynamic>>();
_roles = jsonData.map<Role>((json) => Role.fromJson(json)).toList();
print(_roles[0].roleName);
} catch (error) {
print(error);
throw error;
}
}
}
now I told you that above is not the best practice. Why? Because you will be creating and destroying the http.Client in many different places. Let's pay attention to a better practice.
In almost every app, we use State Management. I am a fan of Provider, it could be anything of your choice. i figured out the best way is to let the state management to remember the creation of http.Client. Since I am using Provider, I created the following class.
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
class ConnectionService with ChangeNotifier {
http.Client _client = http.Client();
http.Client returnConnection() {
return _client;
}
}
And this is my main class
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ConnectionService()),
],
child: MyApp(),
));
}
Now when the app opens, I call the ConnectionService class to make the connection and do my API calls such as checking user authentication, user access etc. And only the first call is taking its time to build the connection, other calls do not.

How to Solve this error CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:359))

I am new to flutter and developing an app but I am facing an issue regarding a post request to the asp.net core API method.
This below is the flutter code that I am using to post data to the asp.net core API method.
Future registerUser(
String userFullName, String username, String email, String password) async {
var body = jsonEncode({
"UserFullName": userFullName,
"Username": username,
"Email": email,
"Password": password
});
final response = await http.post(
Uri.parse(GetURI.baseURI() + 'Account/Register'),
body: body,
headers: {"content-type": "application/json"});
print("Hello Body! " + response.statusCode.toString());
print(response.body);
return null;
}
On button click, I am calling the above function to post data but getting an error. The code for the button click is below:
onPressed: () async {
final validationSuccess = _formKey.currentState!.validate();
if (validationSuccess) {
_formKey.currentState!.save();
final formData = _formKey.currentState!.value;
final userData = await registerUser(
_formKey.currentState!.fields['username']!.value,
_formKey.currentState!.fields['email']!.value,
_formKey.currentState!.fields['email']!.value,
_formKey.currentState!.fields['password']!.value);
print(userData!.userFullName);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$formData'),
duration: Duration(seconds: 5),
));
}
}
On clicking the button it gives me the error like below:
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: HandshakeException: Handshake error in client (OS Error:
CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:359))
This is the Class code from where I am returning my URL:
class GetURI {
static String baseURI() {
return 'https://10.103.78.29:44318/api/';
}
}
My main.dart file
import 'dart:io';
import 'package:bugsmashmobileapp/Screen/LoginScreen/login_body.dart';
import 'package:bugsmashmobileapp/Screen/SignupScreen/signup_body.dart';
import 'package:bugsmashmobileapp/Screen/WelcomeScreen/getting_started_screen.dart';
import 'package:flutter/material.dart';
class MyHttpOverrides extends HttpOverrides {
#override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
}
void main() {
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'BUGSMASH APP',
theme: ThemeData(
primarySwatch: Colors.blue, scaffoldBackgroundColor: Colors.white),
home: GettingStartedScreen(),
routes: {
SignupScreen.routeName: (context) => SignupScreen(),
LoginScreen.routeName: (context) => LoginScreen()
},
);
}
}
I have tried many ways and searched a lot but not able to get a good way to solve this error. If anyone can help it would be appreciated.
When I tried ngrok the problem is solved for me.
First I created an account on ngrok (Click here to go to website) and then you need to install its software to generate a URL and forwarding it to your API Project. Then you can use that URL in your Flutter Project without any issue.

Unable to authenticate using Flutter local_auth plugin

I am using the Flutter local_auth plugin, biometrics works fine but Pincode/pattern doesn't provide authentication. I found if I remove my fingerprints from my mobile then Pincode and pattern authentication works but I need to input 2 times. The library seems correct but couldn't get a proper hold on the reason for this strange behaviour. Can anyone suggest?
Moreover, can we use a custom UI for the authentication, like how it is in WhatsApp?
local_auth_api.dart:
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
import 'package:local_auth/auth_strings.dart';
class LocalAuthApi {
static final _auth = LocalAuthentication();
static const iosStrings = IOSAuthMessages(
cancelButton: 'cancel',
goToSettingsButton: 'settings',
goToSettingsDescription: 'Please set up your Touch ID.',
lockOut: 'Please reenable your Touch ID');
static const androidStrings = AndroidAuthMessages(
cancelButton: 'cancel',
goToSettingsButton: 'settings',
goToSettingsDescription: 'Please set up your Touch ID.',
signInTitle: 'User Authorization Required',
);
static Future<bool> hasBiometrics() async {
try {
return await _auth.canCheckBiometrics;
} on PlatformException catch (e) {
return false;
}
}
static Future<List<BiometricType>> getBiometrics() async {
try {
return await _auth.getAvailableBiometrics();
} on PlatformException catch (e) {
return <BiometricType>[];
}
}
static Future<bool> authenticate() async {
try {
return await _auth.authenticate(
localizedReason: 'Scan your Fingerprint to Authenticate',
useErrorDialogs: true,
sensitiveTransaction: true,
stickyAuth: true,
iOSAuthStrings: iosStrings,
androidAuthStrings: androidStrings
);
} on PlatformException catch (e) {
print(e);
return false;
}
}
}
lock_screen.dart
import 'package:xyz/resources/constants.dart';
import 'package:xyz/src/services/local_auth_api.dart';
import 'package:flutter/material.dart';
class LockScreen extends StatefulWidget{
const LockScreen({Key? key}) : super(key: key);
#override
_LockScreenState createState() => _LockScreenState();
}
class _LockScreenState extends State<LockScreen>{
#override
void initState() {
super.initState();
authenticate();
}
authenticate() async {
bool isAuthenticated = false;
while(true){
isAuthenticated = await LocalAuthApi.authenticate();
if (isAuthenticated) {
print(isAuthenticated);
break;
}
}
print("unlocked");
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
double screenHeight = size.height;
double screenWidth = size.width;
return Scaffold(
backgroundColor: primaryColor,
body: Center(
)
);
}
}
Response:
E/BiometricFragment(19568): Not launching prompt. Client activity was null.

Flutter Plaid package showing blank screen on iOS

Good day,
I have a flutter app which I have integrated a Plaid flutter package, it works well on android but shows a white screen on iOS.
I have added
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
in the Info.plist file, but this doesn't seem to make it work.
Below are screenshots
Please I need help on what to do to make iOS platform work.
Here is my configuration
Configuration configuration = Configuration(
plaidPublicKey: '$PLAID_PUBLIC_KEY',
plaidBaseUrl: 'https://cdn.plaid.com/link/v2/stable/link.html',
plaidEnvironment: '$PLAID_ENV',
environmentPlaidPathAccessToken:
'https://sandbox.plaid.com/item/public_token/exchange',
environmentPlaidPathStripeToken:
'https://sandbox.plaid.com/processor/stripe/bank_account_token/create',
// plaidClientId: 'yourPlaidClientId',
// secret: plaidSandbox ? 'yourSecret' : '',
clientName: '$PLAID_CLIENT_NAME',
// webhook: 'Webhook Url',
products: 'auth, transactions',
selectAccount: 'true',
plaidClientId: null);
FlutterPlaidApi flutterPlaidApi = FlutterPlaidApi(configuration);
WidgetsBinding.instance.addPostFrameCallback((_) {
// Add Your Code here.
});
flutterPlaidApi.launch(context, (Result result) async {
// show loader screen when returning back to the app
showLoadingScreen(context, message: 'Processing...');
// send the data to the api
var response = await BankService().linkUserAccountWithSila(
accountName: result.accountName,
publicToken: result.token,
email: 'email#example.com');
final responseJson = json.decode(response.body);
if (response.statusCode >= 200 && response.statusCode <= 299) {
var token = await getToken();
var client = new http.Client();
List<String> urls = [
'getDefaultAccount',
'all',
];
try {
List<http.Response> list =
await Future.wait(urls.map((urlId) => client.get(
'$kBaseUrl/account/$urlId',
headers: {HttpHeaders.authorizationHeader: "Bearer $token"},
)));
if (list[0].statusCode == 200 && list[1].statusCode == 200) {
var defaultAccount = jsonDecode(list[0].body);
var plaidAccounts = jsonDecode(list[1].body);
Provider.of<TransferProvider>(context, listen: false)
.updatePlaidBankAccounts(
plaidAccount:
plaidAccounts['data'] != null ? plaidAccounts['data'] : [],
account: defaultAccount['data'],
);
}
} catch (e) {} finally {
client.close();
}
Navigator.pop(context);
Toast.show('Account linked successfully', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
} else {
Toast.show('Something went wrong, please try again later', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
// error
}
}, stripeToken: false);
}
Try this code: https://github.com/flutter/flutter/issues/49483
Diclaimer: this is not my code. I am copying it here so that if the original post gets deleted, the source code is still available here. All credits to the original author.
Steps to Reproduce
Register for a free sandbox testing account at Plaid (running the webview in sandbox requires a public_key)
Create new project and add plaid_screen.dart to lib
In plaid_screen.dart assign the public key from Plaid into the queryParameters "key" key
Replace default main.dart content with the setup below
add webview_flutter: ^0.3.19+5 to pubspec.yaml
add <key>io.flutter.embedded_views_preview</key><true/> to ios/Runner/info.plist
Run main.dart
main.dart:
import 'package:bug/plaid_screen.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: PlaidScreen.id,
routes: {
PlaidScreen.id: (context) => PlaidScreen(),
},
);
}
}
plaid_screen.dart:
(Note: public key must be obtained and pasted below)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
String authority = 'cdn.plaid.com';
String unencodedPath = '/link/v2/stable/link.html';
Map<String, String> queryParameters = {
"key": "{{PASTE_PUBLIC_KEY}}",
"product": "auth",
"apiVersion": "v2", // set this to "v1" if using the legacy Plaid API
"env": "sandbox",
"clientName": "Test App",
"selectAccount": "true",
};
// documentation: https://plaid.com/docs/#webview-integration
class PlaidScreen extends StatefulWidget {
static const id = 'plaid_screen_id';
#override
_PlaidScreenState createState() => _PlaidScreenState();
}
class _PlaidScreenState extends State<PlaidScreen> {
Uri uri = Uri.https(authority, unencodedPath, queryParameters);
Completer<WebViewController> _controller = Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: WebView(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: uri.toString(),
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
navigationDelegate: (NavigationRequest navRequest) {
debugPrint("NavigationRequest URL: ${navRequest.url}");
if (navRequest.url.contains('plaidlink://')) {
return NavigationDecision.prevent;
}
debugPrint(navRequest.url.toString());
return NavigationDecision.navigate;
},
),
),
);
}
}

How to achieve Socket IO client data in flutter

Hello I want to achieve Socket IO in flutter, for that I am using the below example but I cannot see the data which I'm emitting to that particular channel, I am using with both emulator and physical device but the word "TEST" which I'm emitting to chat channel, is not printing on console please help me out.
Example:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:socket_flutter_plugin/socket_flutter_plugin.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
#override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
try {
SocketFlutterPlugin myIO = new SocketFlutterPlugin();
myIO.socket("http://10.2.2.22:9006");
myIO.connect();
String jsonData =
'{"content":"test"}';
myIO.emit("chat",jsonData);
myIO.on("chat",(data){
debugPrint(data.toString());
});
} on PlatformException {
_platformVersion = 'Failed to get platform version.';
}
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: new Center(
child: new Text('Running on: $_platformVersion\n'),
),
),
);
}
}
Output ::
Launching lib\main.dart on Android SDK built for x86 in debug mode...
Built build\app\outputs\apk\debug\app-debug.apk.
D/SocketIO (18242): Socket initialised
D/SocketIO (18242): Connected
D/SocketIO (18242): Pushing {"content":"test"} on topic chat
D/SocketIO (18242): registering to chat topic
D/NetworkSecurityConfig(18242): No Network Security Config specified, using platform default
D/ (18242): HostConnection::get() New Host Connection established 0x9fc9d980, tid 18264
D/EGL_emulation(18242): eglMakeCurrent: 0x9c1f32e0: ver 2 0 (tinfo 0x9fc83550)
Using this package flutter_socket_io, I was able to connect to a Socket IO server.
import 'package:flutter_socket_io/flutter_socket_io.dart';
import 'package:flutter_socket_io/socket_io_manager.dart';
...
class _MyAppState extends State<MyApp> {
SocketIO socket;
...
void _onInitSocketIO() {
socket = SocketIOManager().createSocketIO("http://10.2.2.22:9006", "/", query: "", socketStatusCallback: _onSocketStatus);
socket.init();
socket.connect();
}
void _onSocketStatus(dynamic data) {
// If socket connects successfully
if (data == "connect") {
// Send message to server on the 'chat' event.
String jsonData = '{"content":"test"}';
socket.sendMessage('chat', jsonData);
// Subscribe to the 'chat' event.
socket.subscribe("chat", _onReceiveChatEvent);
}
}
void _onReceiveChatEvent(dynamic data) {
debugPrint(data);
}
}
I'd say that you are emiting before listening, so you can't hear what you are emiting (as you are not listening)...
Try inversing the sequence of emit and on like so
myIO.on("chat",(data){
debugPrint(data.toString());
});
myIO.emit("chat",jsonData);
and move the decalaration of myIO to the State level:
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
SocketFlutterPlugin myIO;
// ...
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
try {
myIO = new SocketFlutterPlugin();
//...
}
//...
}
}

Categories

Resources