Support WebSocket close reason with a Dart WebSocket server - websocket

I'm using Dart's WebSocket classes (both the dart:io and dart:html versions) to connect to a Dart WebSocket server. When I have the client close the web socket connection with a custom close code and reason, the custom code is set properly but the custom close reason is not.
Here's a reduced test case:
server.dart
library server;
import 'dart:io';
main() async {
HttpServer server = await HttpServer.bind('localhost', 8081);
server
.transform(new WebSocketTransformer())
.listen((WebSocket webSocket) {
print('WebSocket opened.');
webSocket.listen((_) {}, onDone: () {
print('WebSocket closed.');
});
});
print('Listening..');
}
test.dart
library test;
import 'dart:io';
main() async {
WebSocket webSocket1 = await WebSocket.connect('ws://localhost:8081');
webSocket1.listen((_) {}, onDone: () {
print('Local Dart ws connection: closed with\n\t' +
'code: ${webSocket1.closeCode}\n\t' +
'reason: ${webSocket1.closeReason}');
});
webSocket1.close(4001, 'Custom close reason.');
WebSocket webSocket2 = await WebSocket.connect('ws://echo.websocket.org');
webSocket2.listen((_) {}, onDone: () {
print('echo.websocket.org connection: closed with\n\t' +
'code: ${webSocket2.closeCode}\n\t' +
'reason: ${webSocket2.closeReason}');
});
webSocket2.close(4001, 'Custom close reason.');
}
stdout
Local Dart ws connection: closed with
code: 4001
reason:
echo.websocket.org connection: closed with
code: 4001
reason: Custom close reason.
The first web socket (the one connecting to the local Dart server) connects and closes, but the close reason is missing (blank string). The second web socket (the one connecting to echo.websocket.org) connects and closes, and both the close code and reason are set correctly.
It can't be an issue with the way the client connects, because it's identical in both cases. So is it an issue with the way I'm setting up the Dart WebSocket? Or is it a bug in the WebSocketTransformer/WebSocket class?

This has been addressed with an addition to dart-lang/sdk: https://github.com/dart-lang/sdk/issues/23964

Related

How do you shut down a stale Apollo-Client websocket connection?

I have a page that is loading connected apollo-client pages as widgets inside of a main page. My GraphQL server is self-hosted. They are served up through iFrame (self-hosted), connecting back to my own server. Communications are done through a 3rd party's iFrame communications SDK.
For some reason the widgets are not cleared out from the window when they are stale (I have no control over this). However I do have access to know when they are "stale". When they turn stale I want to disconnect / shutdown the websocket connection. The trouble is the still-connected clients are eating up my back-end's CPU. I am watching the websocket connection through chrome dev-tools. I notice every 5 seconds it sends a keep-alive request to the websocket sever. Every so often I see a stop request, and I want to figure out how to replicate that.
Im my react apollo-connected component I tried calling these two commands, but after they are called with no errors, the keep-alive flags are still being sent to the websocket server.
this.props.client.stop()
this.props.client.clearStore();
How do I tell the apollo-client to shut itself down?
For Apollo V3, the WebSocketLink has an internal SubscriptionClient instance, but the problem is that WebSocketLink doesn't expose methods that give you access to the SubscriptionClient instance, so there's no accessing SubscriptionClient.close(). Fortunately, WebSocketLink accepts a client as an argument:
const subscriptionClient = new SubscriptionClient(`wss://example.com/subscriptions`, {
// example options:
reconnect: true,
lazy: true,
connectionParams: () => ({ accessToken: 'secret' }),
});
const wsLink = new WebSocketLink(subscriptionClient);
Now you just need to move subscriptionClient into a context in order to gain access to the client in various places:
export const SubscriptionClientContext = createContext<
SubscriptionClient | undefined
>(undefined);
export const useSubscriptionClient = (): SubscriptionClient => {
const subscriptionClient = useContext(SubscriptionClientContext);
if (subscriptionClient === undefined) {
throw Error(
'SubscriptionClient not initiated, can only be called inside SubscriptionClientContext.Provider',
);
}
return subscriptionClient;
};
<SubscriptionClientContext.Provider value={subscriptionClient}>
<App />
</SubscriptionClientContext.Provider>
This will let you access methods on the client for logout behavior in various parts of the app:
const subscriptionClient = useSubscriptionClient();
subscriptionClient.close();
There are also two arguments for .close, that have various behaviors. E.g. close and reconnect, close and do not reconnect.

Can't connect to web socket from Electron when using self signed cert

I have an Electron app which tries to connect to a device over a web socket. The connection is encrypted (i.e. wss) but the SSL certificate is self signed and thus, untrusted.
Connecting inside Chrome is ok and it works. However inside Electron I run into problems. Without putting any certificate-error handlers on the BrowserWindow or on the app I receive the following error in the console output:
WebSocket connection to 'wss://some_ip:50443/' failed: Error in connection establishment: net::ERR_CERT_AUTHORITY_INVALID
Then shortly after:
User is closing WAMP connection.... unreachable
In my code, to make the connection I run the following.
const connection = new autobahn.Connection({
realm: 'some_realm',
url: 'wss://some_ip:50443'
});
connection.onopen = (session, details) => {
console.log('* User is opening WAMP connection....', session, details);
};
connection.onclose = (reason, details) => {
console.log('* User is closing WAMP connection....', reason, details);
return true;
};
connection.open();
// alternatively, this also displays the same error
const socket = new WebSocket(`wss://some_ip:50443`);
socket.onopen = function (event) {
console.log(event);
};
socket.onclose = function (event) {
console.log(event);
};
NOTE: Autobahn is a Websocket library for connecting using the WAMP protocol to a socket server of some sort. (in my case, the device) The underlying protocol is wss. Underneath the code above, a native JS new WebSocket() is being called. In other words:
As I mentioned, I've tested this code in the browser window and it works. I've also built a smaller application to try and isolate the issue. Still no luck.
I have tried adding the following code to my main.js process script:
app.commandLine.appendSwitch('ignore-certificate-errors');
and
win.webContents.on('certificate-error', (event, url, error, certificate, callback) => {
// On certificate error we disable default behaviour (stop loading the page)
// and we then say "it is all fine - true" to the callback
event.preventDefault();
callback(true);
});
and
app.on('certificate-error', (event, webContents, link, error, certificate, callback) => {
// On certificate error we disable default behaviour (stop loading the page)
// and we then say "it is all fine - true" to the callback
event.preventDefault();
callback(true);
});
This changed the error to:
WebSocket connection to 'wss://some_ip:50443/' failed: WebSocket opening handshake was canceled
My understanding is that the 'certificate-error' handlers above should escape any SSL certificate errors and allow the application to proceed. However, they're not.
I've also tried adding the following to main.js:
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
webSecurity: false
}
});
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = '1';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
With Election, how do I properly deal with a certificate from an untrusted authority? i.e. a self signed cert.
Any help would be much appreciated.
I had the same problem , all i added was your line:
app.commandLine.appendSwitch('ignore-certificate-errors');
I use socket.io, but i think its the same principal.
I do however connect to the https protocol and not wss directly.
This is what my connection looks like on the page:
socket = io.connect(
'https://yoursocketserver:yourport', {
'socketpath',
secure: false,
transports: ['websocket']
});
That seems to have done the trick.
Thank you for the help :) i hope this answer helps you too.

Flutter websocket disconnect listening

In Flutter, I wanna listen to websocket disconnect event, how to achieve that?
The websocket connect will be drop when app goes to background, I still not found a method to let it continuesly running in background (does anyone have solution?), So I have to detect if a websocket connect is lost or something, so that I can re-connect when lost connection.
Pls help if anyone knows how to achieve that.
You can find out if websocket is closed by implementing onDone callback. See the example below:
_channel = IOWebSocketChannel.connect(
'ws://yourserver.com:port',
);
///
/// Start listening to new notifications / messages
///
_channel.stream.listen(
(dynamic message) {
debugPrint('message $message');
},
onDone: () {
debugPrint('ws channel closed');
},
onError: (error) {
debugPrint('ws error $error');
},
);
Hope that helps.
If your server closes the connection just use pinginterval like this
ws.pingInterval = const Duration(seconds: 5);
onDone should be called.
basic ping pong is enough.
Other answers around SO and the web suggest that you can't just keep sockets open in the background (which seems reasonable, you'd be keeping open network connections that may affect battery life). Depending on your use case, you might be better looking at Push Notifications or something that checks on a schedule.
How to keep iphone ios xmpp connection alive while in the background?
Websocket paused when android app goes to background
https://www.quora.com/How-do-I-keep-Socket-IO-running-in-the-background-on-iOS
WebSocketChannel channel = WebSocketChannel.connect(uri );
Stream stream = channel.stream;
stream.listen((event) {
print('Event from Stream: $event');
},onError: (e){
Future.delayed(Duration(seconds: 10)).then((value) {
connectAndListen();
},);
},
onDone: (() {
Future.delayed(Duration(seconds: 10)).then((value) {
connectAndListen();
},);
})
);
I recommend you to use this multiplatform websocket package https://pub.dev/packages/websocket_universal , there you can even track all WS events happening (and even built-in ping measurment if you need any):
import 'package:websocket_universal/websocket_universal.dart';
/// Example works with Postman Echo server
void main() async {
/// Postman echo ws server (you can use your own server URI)
/// 'wss://ws.postman-echo.com/raw'
/// For local server it could look like 'ws://127.0.0.1:42627/websocket'
const websocketConnectionUri = 'wss://ws.postman-echo.com/raw';
const textMessageToServer = 'Hello server!';
const connectionOptions = SocketConnectionOptions(
pingIntervalMs: 3000, // send Ping message every 3000 ms
timeoutConnectionMs: 4000, // connection fail timeout after 4000 ms
/// see ping/pong messages in [logEventStream] stream
skipPingMessages: false,
/// Set this attribute to `true` if do not need any ping/pong
/// messages and ping measurement. Default is `false`
pingRestrictionForce: false,
);
/// Example with simple text messages exchanges with server
/// (not recommended for applications)
/// [<String, String>] generic types mean that we receive [String] messages
/// after deserialization and send [String] messages to server.
final IMessageProcessor<String, String> textSocketProcessor =
SocketSimpleTextProcessor();
final textSocketHandler = IWebSocketHandler<String, String>.createClient(
websocketConnectionUri, // Postman echo ws server
textSocketProcessor,
connectionOptions: connectionOptions,
);
// Listening to webSocket status changes
textSocketHandler.socketHandlerStateStream.listen((stateEvent) {
// ignore: avoid_print
print('> status changed to ${stateEvent.status}');
});
// Listening to server responses:
textSocketHandler.incomingMessagesStream.listen((inMsg) {
// ignore: avoid_print
print('> webSocket got text message from server: "$inMsg" '
'[ping: ${textSocketHandler.pingDelayMs}]');
});
// Listening to debug events inside webSocket
textSocketHandler.logEventStream.listen((debugEvent) {
// ignore: avoid_print
print('> debug event: ${debugEvent.socketLogEventType}'
' [ping=${debugEvent.pingMs} ms]. Debug message=${debugEvent.message}');
});
// Listening to outgoing messages:
textSocketHandler.outgoingMessagesStream.listen((inMsg) {
// ignore: avoid_print
print('> webSocket sent text message to server: "$inMsg" '
'[ping: ${textSocketHandler.pingDelayMs}]');
});
// Connecting to server:
final isTextSocketConnected = await textSocketHandler.connect();
if (!isTextSocketConnected) {
// ignore: avoid_print
print('Connection to [$websocketConnectionUri] failed for some reason!');
return;
}
textSocketHandler.sendMessage(textMessageToServer);
await Future<void>.delayed(const Duration(seconds: 30));
// Disconnecting from server:
await textSocketHandler.disconnect('manual disconnect');
// Disposing webSocket:
textSocketHandler.close();
}

Autobahn 0.9.5 (AMD) - Error during WebSocket handshake

I'm trying to implement autobahn 0.9.5 on my SPA project using DurandalJS.
var ab = require('autobahn');
live = new ab.Connection(
{
url: 'ws://localhost:8080',
realm: 'realm1'
});
live.onopen = function(session, details)
{
console.log('Autobahn open successfully!', session);
};
live.onclose = function(reason, details)
{
console.log('Autobahn connection lost', reason + ' - ' + details);
};
live.open();
i received an error on firefox and chrome browser
Firefox:
InvalidAccessError: A parameter or an operation is not supported by the underlying object
websocket.close(code, reason);
Chrome:
WebSocket connection to 'ws://localhost:8080/' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
I have no idea what happened..
BEFORE I STARTED WITH - autobahn 0.9.5
I have write simple test on test.html to see if everything setup in backend is correct.
But on this test i currently used autobahn 0.8.2
test.html
<script src="http://autobahn.s3.amazonaws.com/js/autobahn.min.js"></script>
<script>
var conn = new ab.Session(
// Websocket host
'ws://localhost:8080',
// Callback on connection established
function() {
// Once connect, subscribe to channel
conn.subscribe('3', function(topic, data) {
console.log(topic, data);
});
},
// Callback on connection close
function() {
console.warn('WebSocket connection closed');
},
// Additional AB parameters
{'skipSubprotocolCheck': true}
);
</script>
This test working perfectly as what i need, but after I try to implement it inside real project, I can't make autobahn 0.8.2 loaded using requireJS, It keep give me an error ab not defined.
I don't really understand what is happening, according of autobahn getting started, it should work.
and here is how I define it on main.js (requirejs paths and shim config)
requirejs.config({
paths: {
'autobahn' : 'https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min',
'when' : 'https://cdnjs.cloudflare.com/ajax/libs/when/2.7.1/when'
},
shim: {
'autobahn': {
deps: ['when']
}
}
});
Hopefully somebody can help me, I really love to make it working !
Any help will be greatly appreciated!
Thanks
Probably late, but for further reference.
This is probably not a complete answer to SO question.
First of all, everything should be written either for AutobahnJS v0.8.2 (which supports WAMPv1) or for AutobahnJS v0.9.5 (WAMPv2).
Check API documentation.
WAMP v1
AutobahnJS v0.8.2 API - http://autobahn.ws/js/reference_wampv1.html
it loads from http://autobahn.s3.amazonaws.com/js/autobahn.js
global variable for autobahn is ab
connecting with var conn = new ab.Session(wuri, onOpenCallback, onCloseCallback, options);
WAMP v2
AutobahnJS v0.9.5 API - http://autobahn.ws/js/reference.html
it loads from https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min.jgz
global variable for autobahn is autobahn
connecting with new autobahn.Connection({url: wuri, realm: yourRealm});

Is it possible to use socket.io server with pure html5 websockets?

I want to use sockets in my web app. I don't want to use socket.io library on client-side. It's OK for server-side though. Can I do this?
Now with socket.io on server and pure websocket on client I have destroying non-socket.io upgrade error. I've googled that it means that I have to use socket.io-client library on client-side. Is there any way to avoid that? I don't want client to be tight with this library and use pure html5 websocket instead.
If it's not possible what should I use for server to connect with pure html5 websockets?
If someone is curious here is my server code (coffeescript file)
# Require HTTP module (to start server) and Socket.IO
http = require 'http'
io = require 'socket.io'
# Start the server at port 8080
server = http.createServer (req, res) ->
# Send HTML headers and message
res.writeHead 200, { 'Content-Type': 'text/html' }
res.end "<h1>Hello from server!</h1>"
server.listen 8080
# Create a Socket.IO instance, passing it our server
socket = io.listen server
# Add a connect listener
socket.on 'connection', (client) ->
# Create periodical which ends a message to the client every 5 seconds
interval = setInterval ->
client.send "This is a message from the server! #{new Date().getTime()}"
, 5000
# Success! Now listen to messages to be received
client.on 'message', (event) ->
console.log 'Received message from client!', event
client.on 'disconnect', ->
clearInterval interval
console.log 'Server has disconnected'
And here is a client-side
<script>
// Create a socket instance
socket = new WebSocket('ws://myservername:8080');
// Open the socket
socket.onopen = function (event) {
console.log('Socket opened on client side', event);
// Listen for messages
socket.onmessage = function (event) {
console.log('Client received a message', event);
};
// Listen for socket closes
socket.onclose = function (event) {
console.log('Client notified socket has closed', event);
};
};
</script>
I've found this library, seems OK for my needs https://npmjs.org/package/ws

Resources