How to make websocket stream broadcast to many other pages? - websocket

I have a websocket stream being listened:
widget.channel.stream.listen((data) {
print("!!!!new msg: $data");
var dataJson = json.decode(data);
print(dataJson["content"]);
// do my job
setState(() {
_allAnimateMessages.insert(0, newMsg);
});
newMsg.animationController.forward();
});
But, when enter that page again, there was an error says:
Bad state: Stream has already been listened to.
How to make it as broadcast and other pages can receive that broadcast?

Solution for package web_socket_channel:
final channel = IOWebSocketChannel.connect(socketUrl);
final streamController = StreamController.broadcast();
streamController.addStream(channel.stream);
After that simply use streamController.stream to listen web socket events.

You can use broadcasts.
//Here is the solution
StreamController<String> streamController = new StreamController.broadcast(); //Add .broadcast here
//Now you can listen from various places
#override
void initState() {
super.initState();
print("Creating a StreamController...");
//First subscription
streamController.stream.listen((data) {
print("DataReceived1: " + data);
}, onDone: () {
print("Task Done1");
}, onError: (error) {
print("Some Error1");
});
//Second subscription
streamController.stream.listen((data) {
print("DataReceived2: " + data);
}, onDone: () {
print("Task Done2");
}, onError: (error) {
print("Some Error2");
});
streamController.add("This a test data");
print("code controller is here");
}
Font: https://medium.com/#ayushpguptaapg/using-streams-in-flutter-62fed41662e4
When using broadcasts you can have multiple listeners in the same stream.
If you simply use a stream without ".broadcast ()" you can only have one listener

As there are no useful answer, I update my answer here for other reference.
duplicate subscribe stream is a desired behaviour in flutter.
if you are just using a StreamBuilder, the stream can be listen only once. think about it, if your stream can be listen to many other pages or widgets, then data would be repeated.
But if you want using one single stream, and update all widgets
this is really can be occured when develop a complicated app, for example, you are building a chat app, new message comes, you should update many pages UI (your dialog chat ui, your session list ui....), then you should subscribe this streams in many pages, I still not found a proper way to do this, except make this stream to be broadcast, and do your work.

Related

AWS Websocket doesnt receive previous message until new message is sent

Most of the time the messages are passed normally, but a couple messages in particular arent recieved until the recieving client sends a message. This happens everytime for specific methods/messages, but not at all for others.
Example: user1 sends a message, user2 then sends a message to receive message from user1.
Related Material
Deleted question: websocket receives previous message only when new message is sent
Github issue: webSocket client does not receive messages before sending...
We ran into this issue and the solution had to do with how we wrote our promises. We initially used the sample code provided by Amazon
https://github.com/aws-samples/simple-websockets-chat-app/blob/master/sendmessage/app.js#L26
const postCalls = connectionData.Items.map(async ({ connectionId }) => {
try {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
} catch (e) {
if (e.statusCode === 410) {
console.log(`Found stale connection, deleting ${connectionId}`);
await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise();
} else {
throw e;
}
}
});
And I'm pretty sure having an async function as a map function doesn't work properly or reliably (for whatever reason. maybe this is documented somewhere), so we changed it to a simple for loop and it fixed the issue.
for(const connection of connectionData.Items) {
const connectionId = connection.connectionId;
...same logic goes here
}

Issue Broadcasting to Socket.io Rooms of A Namespace

I'm trying to set up a server that can dynamically create many rooms for many namespaces. I'm currently just trying to broadcast to sockets of a room, when a new socket has joined that room.
So far I have been able to broadcast to a specific namespace and my event listeners on the client receives the message. However when I try to broadcast to a room, of a specific namespace, my event listener doesn't receive that message.
I've turned on the Debugger mode and see the socket.io-client:socket emitting the event with the right payload and event type. So I am not sure what I am missing since the documentation also seems fairly straightforward. Any help would be much appreciated. Below is my code.
Server
const colorNs = io.of('/color');
colorNs.on('connection', (socket) => {
const { id } = socket.handshake.query;
const { id:connId } = socket.conn;
if(id) {
socket.join(id);
socket.broadcast.to(id).emit('user:connect', { id: connId });
}
socket.on('disconnect', () => {
const { id } = socket.handshake.query;
const { id:connId } = socket.conn;
socket.broadcast.to(id).emit('user:disconnect', { id: connId });
});
});
Client
const socket = io('/color?id="123"');
socket.on('user:connect', () => console.log('data', data));
Client - Debug Trace
socket.io-parser decoded 2/color,["user:connect",{"id":"IZTTPidF121JCzf9AAAO"}] as {"type":2,"nsp":"/color","data":["user:connect",{"id":"IZTTPidF121JCzf9AAAO"}]} +1ms
browser.js:133
socket.io-client:socket emitting event ["user:connect",{"id":"IZTTPidF121JCzf9AAAO"}] +3ms

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();
}

WebRTC double video-offer message

I'm trying to build a video-chat with a WebRTC. The problem which I have, is that the video-offer is going in both sides like:
PC1 -> send video-offer
PC2 <- handle video-offer message
PC2 -> send video-answer
PC1 <- handle video-answer (when there is debugger breakpoint, the connection is working)
PC2 -> send video-offer
...
I'm sending video-offer message only in negotiationEventHandler:
function handleNegotiationNeededEvent() {
logMessage('HandleNegotiationNeededEvent fired!');
myPeerConnection.createOffer() //tworzymy SDP offer dla drugiego uzytkownika
.then(function (offer) {
return myPeerConnection.setLocalDescription(offer);
})
.then(function () {
sendToWebSocket(msgTypeVideoOffer, senderId, receiverId, JSON.stringify(myPeerConnection.localDescription));
})
.catch(reportError);
}
and handling video-offer / video-answer in two functions
function handleVideoOfferMsg(msg){
logMessage("handle video-offer message call");
var localStream = null;
createPeerConnection();
var desc = new RTCSessionDescription(JSON.parse(msg.messageContent));
debugger;
myPeerConnection.setRemoteDescription(desc)
.then(function () {
return navigator.mediaDevices.getUserMedia(mediaConstraints);
})
.then(function (stream) {
localStream = stream;
document.getElementById("local_video").srcObject = localStream;
myPeerConnection.addStream(localStream);
})
.then(function () { //Utworz odpowiedz
return myPeerConnection.createAnswer();
})
.then(function (answer) { //Ustaw ja sobie jako lokalna
return myPeerConnection.setLocalDescription(answer);
})
.then(function () { //I wyslij SDP do peera
sendToWebSocket(msgTypeVideoAnswer, senderId, receiverId, JSON.stringify(myPeerConnection.localDescription));
})
.catch(handleGetUserMediaError);
}
function handleVideoAnswerMessage(message) {
logMessage("handle video answer message" + message);
var desc = message.messageContent;
myPeerConnection.setRemoteDescription(JSON.parse(desc));
}
The problem is, that handling video-offer is setting a new objects, what probably is a reason of losing a connection.
This is a bug in the Chrome browser. MDN is right, and this works fine in Firefox.
For a basic sendrecv audio+video offer, the answerer should be allowed to add one video and one audio stream and call setLocalDescription without this firing a negotiationneeded event.
Chrome fails to clear negotiationneeded on the answerer side after the expected audio and video have been added, and in fact will keep firing negotiationneeded back and forth forever.
The answer is, that negotiationneeded event is fired every time, when RTCPeerConnection.addStream() function is called with success. So my solution is to check if only inviter has such event handler set:
if(isInviter){
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
}
My question code is based on mozilla WebRTC Signalling sample, which is just wrong implemented, so be careful using it: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling

Socket.IO subscribe to multiple channels

I want to build a simple chat room system on top of Socket.IO where user can create a new chat room and then people can start chatting.
This sound simple but as the Socket.IO 0.9.4 I'm running now, after reading a few SO posts together with the document on socket.io, i'm getting more and more confused. So, I hope that someone can provide me with instruction that WORK with 0.9.4:
I need a way to subscribe to a room. The room name is chosen by user. When a message is posted in a room, the user should receive it. How should I write the server code, how should I write the client code?
A user can join multiple rooms simultaneously.
I want another system to send a message to all user in a certain room. This 'another system' send the message through a request handled by express. How would I write that request handler?
This is all pretty straightforward with the socket.io rooms feature. Take a look at the documentation on LearnBoost wiki.
https://github.com/LearnBoost/socket.io/wiki/Rooms
It allows for being connected to multiple rooms over a single socket. I put together a quick test with the following code.
Server
io.sockets.on('connection', function(client){
client.on('subscribe', function(room) {
console.log('joining room', room);
client.join(room);
})
client.on('unsubscribe', function(room) {
console.log('leaving room', room);
client.leave(room);
})
client.on('send', function(data) {
console.log('sending message');
io.sockets.in(data.room).emit('message', data);
});
});
Client
var socket = io.connect();
socket.on('message', function (data) {
console.log(data);
});
socket.emit('subscribe', 'roomOne');
socket.emit('subscribe', 'roomTwo');
$('#send').click(function() {
var room = $('#room').val(),
message = $('#message').val();
socket.emit('send', { room: room, message: message });
});
Sending a message from an Express route is pretty simple as well.
app.post('/send/:room/', function(req, res) {
var room = req.params.room
message = req.body;
io.sockets.in(room).emit('message', { room: room, message: message });
res.end('message sent');
});

Resources