SignalR server doesn't consistently call methods on client - angularjs

I have an AngularJS application that I intend to have receive communications via SignalR from the server, most notably when data changes and I want the client to refresh itself.
The following is my hub logic:
[HubName("update")]
public class SignalRHub : Hub
{
public static void SendDataChangedMessage(string changeType)
{
var context = GlobalHost.ConnectionManager.GetHubContext<SignalRHub>();
context.Clients.All.ReceiveDataChangedMessage(changeType);
}
}
I use the following within my API after the data operation has successfully occurred to send the message to the clients:
SignalRHub.SendDataChangedMessage("newdata");
Within my AngularJS application, I create a service for SignalR with the following javascript that's referenced in the HTML page:
angular.module('MyApp').value('signalr', $.connection.update);
Within the root for the AngularJS module, I set this up with the following so that it starts and I can see the debug output:
$(function () {
$.connection.hub.logging = true;
$.connection.hub.start();
});
$.connection.hub.error(function(err) {
console.log('An error occurred: ' + err);
});
Then I've got my controller. It's got all sorts of wonderful things in it, but I'll show the basics as relate to this issue:
angular.module('MyApp').controller('MyController', function($scope, signalr) {
signalr.client.ReceiveDataChangedMessage = function dataReceived(changeType) {
console.log('DataChangedUpdate: ' + changeType);
};
});
Unfortunately, when I set a breakpoint in the javascript, this never executes though the rest of the program works fine (including performing the operation in the API).
Some additional (hopefully) helpful information:
If I set a breakpoint in the SignalRHub class, the method is successfully called as expected and throws no exceptions.
If I look at Fiddler, I can see the polling operations but never see any sign of the call being sent to the client.
The Chrome console shows that the AngularJS client negotiates the websocket endpoint, it opens it, initiates the start request, transitions to the connected state, and monitors the keep alive with a warning and connection lost timeout. There's no indication that the client ever disconnects from the server.
I reference the proxy script available at http://localhost:port/signalr/hubs in my HTML file so I disregard the first error I receive stating that no hubs have been subscribed to. Partly because the very next message in the console is the negotiation with the server and if I later use '$.connection.hub' in the console, I'll see the populated object.
I appreciate any help you can provide. Thanks!

It's not easy to reproduce it here, but it's likely that the controller function is invoked after the start of the connection. You can verify with a couple of breakpoints on the first line of the controller and on the start call. If I'm right, that's why you are not called back, because the callback on the client member must be defined before starting the connection. Try restructuring your code a bit in order to ensure the right order.

Related

Request Deferrer with Service Worker in PWA

I am making a PWA where users can answer the forms. I want it to make also offline, so when a user fills out a form and does not have the internet connection, the reply will be uploaded when he is back online. For this, I want to catch the requests and send them when online. I wanted to base it on the following tutorial:
https://serviceworke.rs/request-deferrer_service-worker_doc.html
I have managed to implement the localStorage and ServiceWorker, but it seems the post messages are not caught correctly.
Here is the core function:
function tryOrFallback(fakeResponse) {
// Return a handler that...
return function(req, res) {
// If offline, enqueue and answer with the fake response.
if (!navigator.onLine) {
console.log('No network availability, enqueuing');
return;
// return enqueue(req).then(function() {
// // As the fake response will be reused but Response objects
// // are one use only, we need to clone it each time we use it.
// return fakeResponse.clone();
// });
}
console.log("LET'S FLUSH");
// If online, flush the queue and answer from network.
console.log('Network available! Flushing queue.');
return flushQueue().then(function() {
return fetch(req);
});
};
}
I use it with:
worker.post("mypath/add", tryOrFallback(new Response(null, {
status: 212,
body: JSON.stringify({
message: "HELLO"
}),
})));
The path is correct. It detects when the actual post event happens. However, I can't access the actual request (the one displayed in try or fallback "req" is basically empty) and the response, when displayed, has the custom status, but does not contain the message (the body is empty). So somehow I can detect when the POST is happening, but I can't get the actual message.
How to fix it?
Thank you in advance,
Grzegorz
Regarding your sample code, the way you're constructing your new Response is incorrect; you're supplying null for the response body. If you change it to the following, you're more likely to see what you're expecting:
new Response(JSON.stringify({message: "HELLO"}), {
status: 212,
});
But, for the use case you describe, I think the best solution would be to use the Background Sync API inside of your service worker. It will automatically take care of retrying your failed POST periodically.
Background Sync is currently only available in Chrome, so if you're concerned about that, or if you would prefer not to write all the code for it by hand, you could use the background sync library provided as part of the Workbox project. It will automatically fall back to explicit retries whenever the real Background Sync API isn't available.

Block or extend Breeze metadata auto fetching

Current application - Angular application with Breeze. Application has ~7 entity managers and different data domains (metadata). When application runs we trying to fetch entity managers, like:
app.run(['$rootScope', 'datacontext1', ... ], function($rootScope, datacontext1, ...) {
datacontext1.loadMetadata();
...
datacontext7.loadMetadata();
}
Every datacontext has its own entity manager and loadMetadata is:
function loadMetadata() {
manager.fetchMetadata().then(function(mdata) {
if (mdata === 'already fetched') {
return;
}
...
applyCustomMetadata(); // Do some custom job with metadata/entity types
});
}
Metadata comes from server asynchronously. Few module has really big metadata, like 200Kb and takes some time for loading and apply to entity manager. Its possible that first Breeze data request executed in same entity manager will be started before this loadMetadata operation finished and as I understand Breeze automatically fetch metadata again. Usually its not a problem, Metadata end point cached on server, but sometimes it produces very strange behavior of Breeze - EntityManager.fetchMetadata resolve promise as "already fetched" and in this case applyCustomMetadata() operation can not be executed.
As I understand problem is inside Breeze and approach its used to resolve metadata promise (seems to be http adapter is singleton and second request override metadata with "already fetched" string and applyCustomMetadata() operation never executes).
Need to figure out some way to resolve issue without significant changes in application.
Logically need to delay full application from using entity managers while loadMetadata done. Looking for any way on Breeze level to disable auto fetch metadata if its already in progress (but not interrupt request, just wait and try again after some time). Any other ideas are fine as well.
Why are you allowing queries to execute before the metadata is loaded? Therein lies your problem.
I have an application bootstrapper that I expose through a global variable; none of my application activities depending on the Entity Manager are started until preliminary processes complete:
var bootstrapper = {
pageReady: ko.observable(false)
};
initBootstrapper();
return bootstrapper;
function initBootstrapper() {
window.MyApp.entityManagerProvider.initialize() // load metadata, lookups, etc
.then(function () {
window.MyApp.router.initialize(); // setup page routes, home ViewModel, etc
bootstrapper.pageReady(true); // show homepage
});
};
Additionally, depending on the frequency of database changes occurring in your organization, you may wish to deliver the metadata to the client synchronously on page_load. See this documentation for further details:
http://breeze.github.io/doc-js/metadata-load-from-script.html

PubNub subscribe message callback not firing

I'm having issues getting PubNub's subscribe message handler to fire. I'm working on a web client that will listen for messages from mobile apps. Up until recently, this code worked fine. I could send a message from my phone and see the web app get auto-updated. But in the last few days, the web app is no longer getting updated.
It's an Angular app that I've been writing in CoffeeScript. I have a MessageService that handles all the bootstrapping for PubNub. The subscribe method of my service is passed an entity id arg to set as the channel name to listen on, and passes a function reference via the messageHandler argument.
angular.module('exampleApp').service 'MessageService', ($http, $interval) ->
pubnub = null
subscribePromise = null
config =
subscribe_key: 'demo'
# Sanity check. This gets triggered upon connection with the correct
# channel name/entity id.
connectionHandler = ->
_.forOwn arguments, (arg) -> console.log arg
return {
getChats: (id) ->
# Calls an API to fetch all of the chat messages. These aren't transmitted over
# PubNub because we do other fun things to adhere to HIPAA compliance.
return $http.get 'path/to/api/endpoint/' + id
subscribe: (id, messageHandler) ->
pubnub = pubnub or PUBNUB.init config
pubnub.subscribe({
channel: id
message: (data) ->
if not not subscribePromise
$interval.cancel subscribePromise
subscribePromise = null
messageHandler data
connect: connectionHandler
})
# Interval-based workaround to function in spite of PubNub issue
subscribePromise = $interval messageHandler, 10000
}
Here's an example of the messageHandler implementation in one of my controllers.
angular.module('exampleApp').controller 'MessageCtrl', (MessageService) ->
$scope.messageId = 'some entity id'
# This message handler never gets fired, despite passing it to pubnub.subscribe
onMessageUpdated = (data) ->
console.log data
MessageService.getChats($scope.messageId).then (messages) -> $scope.messages = messages
MessageService.subscribe $scope.messageId, onMessageUpdated
Like I mentioned, this code was working not long ago, but out of the blue, the message handler stopped firing at all. Haven't touched it in more than a month. The thing that's driving me nuts is that I can open up the dev console in PubNub and watch the messages come in from the phones, but for some reason, that message handler never seems to get called.
I'm using the "edge" version of pubnub.js, so I'm wondering if there was some recent update that broke my implementation. Anything else you folks can see that I may be missing or doing wrong? Any help is appreciated.
// Edit
Just a quick update. I've tried rolling back as far as 3.5.47 and still no change in behavior. I coded a quick workaround using Angular's $interval service to allow the app to at least function while I get this issue figured out. Updated code example above w/ relevant changes.
Quick update. After moving on to some other tasks and circling back to this after a year or so, we decided to take another stab at the problem. The interval-based polling was working fine for our initial implementation as described above, but we now have need for a more robust set of features.
Anyway, we ended up grabbing the latest stable version of the JS client (3.7.21), and so far it appears to have fixed our issue.

SignalR: How to add client call after the hub is started?

First off, I just started trying to add SignalR 2 to my existing Angular SPA project.
I have a main controller which starts the hub right away that is feeding some messages to the client. Inside, I have several sub pages and each could subscribe to a different hub for services. The problems is that the client doesn't receive message because it is hooked up after the hub is already started in the main controller.
As a test, if I comment out the hub start in the main controller, the one in the sub controller works fine.
From what I read, it is by design that you have to hook up all client calls before starting the hub. I don't understand...if it is a service, I should be able to subscribe or unsubscribe anytime after the hub is started. Why not? How to workaround?
Because no response in the 12 hours (which is quite unusual in so), I had to dig around myself. I think, I was misled by the answers from SO on related questions that you have to subscribe all client call before starting the connection, as mentioned e.g. here. I found in Hubs API Guide, one section says
Define method on client (without the generated proxy, or when adding
after calling the start method)
So, it is possible to add client method after connection is started. The trick is to use so-called "without the generated proxy". That limitation is for "with generated proxy".
The following is my working example taken from SignalR get started tutorial.
This is the main controller using "with generated proxy":
$.connection.statusHub.client.updateStatus = function (status) {
$scope.status = status;
$scope.$apply();
}
$.connection.hub.start();
This is in a subcontroller using "without generated proxy":
var connection = $.hubConnection();
var proxy = connection.createHubProxy('stockTickerHub');
proxy.on('updateStockPrice', function (stock) {
var st = $scope.stocks.firstOfKey(stock.symbol, 'symbol');
st.lastPrice = stock.lastPrice;
$scope.$apply();
});
var hub = $.connection.stockTickerHub;
connection.start().done(function () {
hub.server.getAllStocks().done(function (stocks) {
$scope.stocks = stocks;
});
});
Note that it doesn't work if I use "with generated proxy" in the subcontroller like this:
var hub = $.connection.stockTickerHub;
hub.client.updateStockPrice = function (stock) {
var st = $scope.stocks.firstOfKey(stock.symbol, 'symbol');
st.lastPrice = stock.lastPrice;
$scope.$apply();
};
$.connection.hub.start().done(function () {
hub.server.getAllStocks().done(function (stocks) {
$scope.stocks = stocks;
});
});
To prove the limitation of "with generated proxy" mode, this code works if I comment out the one in the main controller.
By the way, I was so confused by the term with or without generated proxy in the Guide, and in both cases, it is still called xxxProxy. Can't they find a better name? Or somebody has an explanation?

Google App Engine Channel API with custom domains

In my GAE app (Python) I have implemented multitenancy and multi-site support based on the host part of the request object.
For example, www.foo.com/index.html and www.bar.com/index.html are both handled by the same app (e.g. myapp.appspot.com). The app reads the host-value and then decides which namespace and site-configuration to use. This works great as long as the app receives the request directly from the user-agent.
However, I would like to use the Channel API, but there is an issue because requests to /_ah/channel/connected/ and /_ah/channel/disconnected/ are not originating from the original user-agent. Instead, the request has Host: myapp.appspot.com and the parameter to=myapp.appspot.com. (The from parameter is the token I expect. Also www.foo.com/_ah/channel/jsapi is redirected to a talkgadget server which is not documented but seems to be as expected.)
I assume, the problem is caused by the code in channel.js which doesn't call my app using the original host, e.g. www.foo.com/_ah/channel/connected. Instead it uses a talkgadget.google.com host which (as far as I can tell) will then call my app, but using myapp.appspot.com, ignoring the original host and so I cannot use the request's host value for my purpose.
As a workaround, I can figure out a way of including the host information into the channel token, so when my connected and disconnected handlers receive the token, they can use the token instead.
However, I would like to know if there is a better approach, where I could still get the original host name (e.g. www.foo.com) requests to /_ah/channel/connected/ and /_ah/channel/disconnected/. Any ideas?
This is what I have tried so far (with-out any success):
Adding the custom domain host name to the JS src attribute:
<script type="text/javascript" src="//www.foo.com/_ah/channel/jsapi"></script>
I also tried to manually override the base-url of the channel socket, suggested here: https://stackoverflow.com/questions/16558776/google-app-engine-channel-api-same-origin-policy
<script type="text/javascript">
onOpened = function() {
// TODO
};
onMessage = function() {
// TODO
};
onError = function() {
// TODO
};
onClose = function() {
// TODO
};
goog.appengine.Socket.BASE_URL = "https://www.foo.com/_ah/channel/";
channel = new goog.appengine.Channel('{{channelToken}}');
socket = channel.open();
socket.onopen = onOpened;
socket.onmessage = onMessage;
socket.onerror = onError;
socket.onclose = onClose;
</script>
I couldn't find any official documentation for channel.js and I don't want to implement something that is going to break easily with the next update by Google.
Short of a proxy, I don't see a better way than including the information in-band. The problem is that the library/infrastructure (can't be certain without looking deeper) is stripping the HTTP-layer information (the Host header), and indeed you don't have any control of the HTTP layer to pass custom headers, etc. So, you either need to have the info at a lower layer (TCP doesn't even provide a means to do this, and since the entrypoint of your code is through the browser running channel.js, rather than a system-level process running on the bare network interface, this is out of the picture decisively), or at a higher layer, ie. within the channel.

Resources