broadcaster action called twice issue in angularjs 4 with ng2-cabel - angular-broadcast

I m using ng2-cable for chat and broadcast any action from server in rails
AngularJs
--> In app.component.ts
import { Ng2Cable, Broadcaster } from 'ng2-cable';
--> used this for connection
private ng2cable: Ng2Cable,
this.ng2cable.subscribe(environment.apiEndpoint+"/cable", 'ChatChannel',{room: this.currentUser.id});
--> Read notification from server
import { Broadcaster } from 'ng2-cable';
private broadcaster: Broadcaster,
this.broadcaster.on('ExpertQuestionList').subscribe(message => {
console.log("This called multiple time when you come back to this page after once load")
}
);
Here, ExpertQuestionList is one action name. It will broadcast from rails server using action cable.
------------------------------------------------------------------------------------------------------------------------
ExpertQuestionList broadcast one time from server, i can see that in my rails log but called twice in console.log. Twice means it repeat multiple time same as how many time you will come on this page. so simple in reinitialise many time. I want something like reset or destroy actions (this.broadcaster.on('ExpertQuestionList'))

Related

Redirect API call fetches from Service Worker

This is a really annoying issue. I am using a third party login in my application. When a user logins in through the third party, it redirects an api call to the server.
ex: /api/signin/github?code=test&state=test
For some strange reason this API call is getting fetched from the service worker instead on the server which handles the login logic.
ex:
Without seeing your service worker's fetch event handler, it's hard to say exactly what code is responsible for that.
In general, though, if there are URLs for which you want to tell the service worker never to respond to, you can just avoid calling event.respondWith(...) when they trigger a fetch. There are lots of ways to avoid doing that, but an early return is straightforward:
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.pathname === '/api/signin/github') {
// By returning without calling event.respondWith(),
// the request will be handled by the normal browser
// network stack.
return;
}
// Your fetch event response generation logic goes here.
event.respondWith(...);
});

Managing Server Side Events with a Service Worker

I am building a web app to display on my iPad to control my raspberry pi acting as an audio recorder. Part of the need is to maintain an event source open so that the server can send Server Side Events. A specific instance of the app can grab control of the recording process, but will loose control if the server sees sse link closes. This is just protection against a client disappearing and leaving the control held (control of the process does needed to be renewed at least every 5 minutes - but I don't really want to wait that long in the normal case of someone just closing the browser tab.)
Part of my need is to push the browser to the background so I can then open up the camera and record a video.
I built this app and had it almost working see https://github.com/akc42/pi_record.git (master branch).
Until I pushed the browser to the background and found IOS shut down the page and broke the sse link.
I tried restructuring to use a private web worker to manage the sse link, massing messages between the web worker and the main javascript thread - again almost working (see workers branch of above repository). But that got shutdown too!
My last thought is to use a service worker, but how to structure the app?
Clearly the service worker must act as a client to the server for the server side events. It must keep the connection open, but it also needs to keep track of multiple tabs in the browser which may or may not try and grab control of the interface, and only allow one tab to do so.
I can think of three approaches - but its difficult to see which is better. At least I have never even seen any mention of approach 2 and 3 below , but it seems to me that one of these two might actually be the simplest.
Approach 1
Move the code I have now for separate web workers into the service worker. However we will need to add to the message passing some form of ID between window and service. So I can record which tab actually grabbed control of the interface and therefore exclude other tabs from doing so (ie simulate a failed attempt to take control).
As far as I can work out MessageEvent.ports[0] could be a unique object which I could store in a Map somewhere, but I am not entirely convinced that the MessageChannel wouldn't close if the browser moved to the background.
Approach 2
have a set of phantom urls in the service worker that simulate all the different message types (and parameters) that where previously sent my the tab to its private web worker.
The fetch event provides a clientid (which I can use to difference between who actually grabbed control) and which I can use to then do Clients.get(clientid).postMessage() (or Clients.matchAll when a broadcast response is needed)
Code would be something like
self.addEventListener('fetch', (event) => {
const requestURL = new URL(event.request.url);
if (/^\/api\//.test(requestURL.pathname)) {
event.respondWith(fetch(event.request)); //all api requests are a direct pass through
} else if (/^\/service\//.test(requestURL.pathname)) {
/*
process these like a message passing with one extra to say the client is going away.
*/
if (urlRecognised) {
event.respondWith(new Response('OK', {status: 200}));
} else {
event.respondWith(new Response(`Unknown request ${requestURL.pathname}`, {status: 404}));
}
} else {
event.respondWith(async () => {
const cache = await caches.open('recorder');
const cachedResponse = await cache.match(event.request);
const networkResponsePromise = fetch(event.request);
event.waitUntil(async () => {
const networkResponse = await networkResponsePromise;
await cache.put(event.request, networkResponse.clone());
});
// Returned the cached response if we have one, otherwise return the network response.
return cachedResponse || networkResponsePromise;
});
}
});
The top of the the fetch event just passes the standard api requests made by the client straight through. I can't cache these (although I could be more sophisticated and perhaps pre reject those not supported).
The second section matches phantom urls /service/something
The last section is taken from Jake Archibald's offline cookbook and tries to use the cache, but updates the cache in the background if any of the static files have changed.
Approach 3
Similar to the approach above, in that we would have phantom urls and use the clientid as a unique marker, but actually try and simulate a server side event stream with one url.
I'm thinking the code with be more like
...
} else if (/^\/service\//.test(requestURL.pathname)) {
const stream = new TransformStream();
const writer = stream.writeable.getWriter();
event.respondWith(async () => {
const streamFinishedPromise = new Promise(async (resolve,reject) => {
event.waitUntil(async () => {
/* eventually close the link */
await streamFinishedPromise;
});
try {
while (true) writer.write(await nextMessageFromServerSideEventStream());
} catch(e) {
writer.close();
resolve();
}
});
return new Response(stream.readable,{status:200}) //probably need eventstream headers too
}
I am thinking that approach 2 could be the simplest, given where I am now but I am concerned that I can see nothing when searching for how to use service workers that discusses this phantom url approach.
Can anyone comment on any of these approaches and provide guidance on how to best program the tricky bits (for instance does Approach 1 message channel close when the browser is moved to the background on an iPad, or how do you really keep a response channel open, and does that get closed when the browser moves to the background in Approach 3)
The simple truth is that none of these approaches will work. What I didn't realise when I asked the question is that a service worker is re-run by the browser when ever there is something to do and that run only lasts for the length of time of the processing of an event. Although eventWaitUntil can prolong that, the only reference to how long I can find is that the browser is still at liberty to cancel it if it appears it might never close. I can't imagine than in a period of several hours it won't get cancelled. So an Event Source will close effectively terminate its link to the server.
So my only option to achieve what I want is to have the server carry on when the Event Source closes and find some other mechanism to release resources held on behalf of the client

Slow Startup for setup SignalR connection

Setup a simple SignalR Chat web application using the Microsoft tutorial code.
!--SignalR script to update the chat page and send messages.-->
<script>
$(function () {
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
// Create a function that the hub can call back to display messages.
chat.client.addNewMessageToPage = function (name, message) {
// Add the message to the page.
$('#discussion').append('<li><strong>' + htmlEncode(name)
+ '</strong>: ' + htmlEncode(message) + '</li>');
};
// Get the user name and store it to prepend to messages.
$('#displayname').val(prompt('Enter your name:', ''));
// Set initial focus to message input box.
$('#message').focus();
// Start the connection.
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.send($('#displayname').val(), $('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
});
});
It's running OK when debugging locally, I can send message immediately after putting in a username. But when deployed onto the Azure, after entering the username, I have to wait like 5 seconds before I can submit a new message (no response clicking Send button), but after this first message, for all the following messages I can send instantly.
For me, it looks like it is slow when setting up the initial connection ($.connection.hub.start()).
Is this normal? How can I improve the performance of this simple application?
By default websockets are not enabled on Azure and, by default, the client tries different transports starting from webSockets. If websockets does not work it will fall back to serverSentEvents and finally to longPolling. This takes time. Make sure you turn on websockets on Azure or specify that you want to use only serverSentEvents and longPolling transports.

SignalR is only sending the first two messages

I have a .Net MVC web application and I am using SignalR to implement a progress bar functionality.
I have a View that is making an Ajax POST to an action using JQuery:
$.ajax({
url: actionUrl,
type: 'POST',
data: { ids: ids },
success: function (data) {
...
}
});
The Controller is procesing information inside a loop, and every n iteration is sending a message using signalr to a Hub. The client is connected to the Hub and updates a progress bar with the information in the messages.
I open the connection to the signalr hub with this code:
var connection = new HubConnection("http://localhost/");
connection.Credentials = CredentialCache.DefaultNetworkCredentials;
this.proxy = connection.CreateHubProxy("progressHub");
connection.Start().Wait();
And then send the information with this:
proxy.Invoke("ProgressChanged", taskId, progress);
This is working correctly for the first two updates, but from that on it is not working anymore until the end of the long process, when I receive all the remaining messages. I am receiving something like this:
0% complete
5% complete
... (long pause as the process completes)
10%, 15%, 20%, 25%, etc (all these messages come together)
It is always the first two messages, not a random number of messages.
Do you know any configuration that I may be missing?
I tried adding a sleep after each message and making the action async and adding .Wait() to each message, but it is always the same behaviour.
I tried it in IISExpress and full IIS 8.
Your action method in the controller should be async and call an asynchronous Task. My guess is that while your ajax call is waiting for completion, the call made by signalR are probalby queued until your proccess is complete and end the ajax request.

How to prevent spam when using websocket-rails gem?

Consider the following to implement tic-tac-toe:
One player sends a move by triggering an event on the main dispatcher.
var dispatcher = new WebSocketRails('localhost:3000/websocket');
var channel = dispatcher.subscribe_private('private_game');
channel.bind('new_move', function(move) {
// received new move, process it
});
// later on when we want to send a move to the server we run the following code
var move = {
square: ...;
}
dispatcher.trigger('move', move);
On the server the controller can verify that the user is authorized for that specific game of tic-tac-toe. And then it can broadcast the move to both players.
class TicTacToeController < WebsocketRails::BaseController
def move
# code to verify the move is valid and save to database
...
# broadcast move to all players
WebsocketRails[:private_game].trigger(:new_move, message)
end
end
But there is nothing to enforce that the client sends messages only using the main dispatcher. The 'private_game' channel is suppose to be used only by the server for broadcasting moves. But a hostile client could send random data on it with
channel.trigger('new_move', randomdata);
Since channel events do not go through the Event Router and thus don't go through a Controller action, there is nothing on the server side to filter out the random spam.
Is there a way to stop random data spam on the server? Perhaps I'm misunderstanding how to use websocket-rails?
One way you could handle this now before the Gem is updated to support this better would be to use a separate private channel for each user.
Example
Game Starts
User A connects to private channel named user_a_incoming_moves
User B connects to private channel named user_b_incoming_moves
When user B makes a move, you broadcast that on the user_a_incoming_moves private channel that only User A is connected to.
When user A makes a move, you broadcast that on the user_b_incoming_moves channel that only User B is connected to.
This would prevent anyone from being able to send malicious moves through.
You can read more about private channels in the Private Channel Wiki.

Resources