Connect to SignalR Hub after connection start - javascript

Let's say i have two or more hubs in my server application. My javascipt client (Angular SPA) initialy needs a connection to the first hub, and needs to subscribe to a method like this:
connection = $.hubConnection(appSettings.serverPath);
firstHubProxy = connection.createHubProxy('firstHub');
firstHubProxy('eventFromFirstHub', function () {
console.log('Method invokation from FirstHub');
});
connection.start().done(function (data) {
console.log("hub started");
});
Everything is working fine. Now a user of my Angular SPA may decide to put a widget on his page, which needs to subcribe to a method from the second hub:
secondHubProxy = connection.createHubProxy('firstHub');
secondHubProxy('eventFromSecondHub', function () {
console.log('Method invokation from SecondHub');
});
The method from the second hub is not working. I guess because it was created after connection.start().
My example is simplified, in my real appplication there will be 20+ hubs to which users may or may not subscribe by adding or removing widgets to their page.
As far as i can tell i have two options:
call connection.stop() and then connection.start(). Now both hub subscriptions are working. This just doesn't feel right, because on all hubs, the OnConnected() event fires, and my application will be starting and stopping all the time.
create hub proxy objects for all possible hubs, subscribe to a dummy
method on all possible hubs, so the application can subscibe to hub
methods later if desired. This also doesn't feel right, because i
need to create 20+ hub proxies, while i may need just a few of
those.
Is anybody aware of a pattern which i can use to accomplish this? Or am i missing something very simple here?

Personally I use #2. I have a hub service that subscribes to all client methods. Any of my other angular components then pull that hub service in and subscribe to its events as needed.
Here it is;
hub.js
(function () {
'use strict';
angular
.module('app')
.factory('hub', hub);
hub.$inject = ['$timeout'];
function hub($timeout) {
var connection = $.connection.myHubName;
var service = {
connect: connect,
server: connection.server,
states: { connecting: 0, connected: 1, reconnecting: 2, na: 3, disconnected: 4 },
state: 4
};
service = angular.extend(service, OnNotify());
activate();
return service;
function activate() {
connection.client.start = function (something) {
service.notify("start", something);
}
connection.client.anotherMethod = function (p) {
service.notify("anotherMethod", p);
}
// etc for all client methods
$.connection.hub.stateChanged(function (change) {
$timeout(function () { service.state = change.newState; });
if (change.state != service.states.connected) service.notify("disconnected");
console.log("con:", _.invert(service.states)[change.oldState], ">", _.invert(service.states)[change.newState]);
});
connect();
}
function connect() {
$.connection.hub.start({ transport: 'auto' });
}
}
})();
OnNotify
var OnNotify = function () {
var callbacks = {};
return {
on: on,
notify: notify
};
function on(name, callback) {
if (!callbacks[name])
callbacks[name] = [];
callbacks[name].push(callback);
};
function notify(name, param) {
angular.forEach(callbacks[name], function (callback) {
callback(param);
});
};
}
Then I can subscribe to things as needed, for example in a controller;
(function () {
'use strict';
angular
.module('app')
.controller('MyController', MyController);
MyController.$inject = ['hub'];
function MyController(hub) {
/* jshint validthis:true */
var vm = this;
vm.something = {};
hub.on('start', function (something) {
$timeout(function () {
console.log(something);
vm.something = something;
});
});
}
})();

Related

Managing multiple SignalR connections in a single page

I'm experiencing intermittent signalr connection problems
sometimes it fails, sometimes it doesn't...
Here is the setup...
I have a list of orders, each order has a unique signalr connection. Currently there are 230 orders on a single page. The purpose of having a signalr connection is so users can see any real time updates on each order (who is viewing, editing, etc). I've decided to have a separate connection for each order so that I don't have to manage the order that is currently being viewed, edited, etc. So far, for the orders that have successfully connected, the updates are correct and smooth.
Here is my list with a sample of another user viewing an order (a photo of that user is being shown)
Here is my code that connects to the signalr hubs
crimeassure.factory('hubProxy', ['$rootScope', function ($rootScope) {
function hubProxyFactory(hubName) {
var _hubConnection = $.hubConnection();
_hubConnection.logging = true;
var _hubProxy = _hubConnection.createHubProxy(hubName);
return {
on: function (eventName, callback, failCallback) {
_hubProxy.on(eventName, function (result) {
$rootScope.$apply(function () {
if (callback) {
callback(result);
}
});
})
},
invoke: function (methodName, data) {
_hubProxy.invoke(methodName, data)
.done(function (result) {
//$rootScope.$apply(function () {
// if (callback) {
// callback(result);
// }
//});
});
},
start: function (successCallback, failCallback) {
_hubConnection.start({ transport: 'webSockets' }).done(successCallback).fail(failCallback);
},
hubConnection: _hubConnection,
};
};
return hubProxyFactory;
}]);
crimeassure.directive('componentLiveUpdates', function () {
return {
restrict: 'E',
scope: {
componentId: '=',
},
templateUrl: '/scripts/templates/directive-templates/component-live-updates.html',
controllerAs: 'vm',
bindToController: true,
controller: ["$scope", "$rootScope", "appData", "hubProxy",
function componentLiveUpdates($scope, $rootScope, appData, hubProxy) {
var vm = (this);
var user = appData.getCurrentUser();
vm.componentActivity = [];
var reQueueHub = hubProxy('researcherExpressQueueHub');
var componentActivityChanged = function (component) {
if (component.ActivityValue === 'ComponentModalClose') {
var idx = vm.componentActivity.indexOf(component);
vm.componentActivity.splice(idx, 1);
}
if (component.ActivityValue === 'ComponentModalOpen') {
vm.componentActivity.push(component);
}
}
var successCallback = function () {
console.log('connected to signalR, connection ID =' + reQueueHub.hubConnection.id + '--' + vm.componentId);
reQueueHub.invoke('joinGroup', vm.componentId);
$rootScope.reQueueHubs.push({
ComponentId: vm.componentId,
Hub: reQueueHub
});
}
var failCallback = function (e) {
console.log('Error connecting to signalR = ' + vm.componentId);
console.log(e);
//startHubConnection();
}
var startHubConnection = function () {
reQueueHub.start(successCallback, failCallback);
}
var initialize = function () {
reQueueHub.on('updateComponentActivity', componentActivityChanged);
startHubConnection();
}
initialize();
}],
}
});
and here is my hub class
public class ResearcherExpressQueueHub : Hub
{
public void UpdateComponentActivity(ComponentItem item)
{
Clients.Group(item.ComponentId.ToString()).updateComponentActivity(item);
}
public void ComponentModalOpen(ComponentItem item)
{
item.Activity = ComponentActivity.ComponentModalOpen;
Clients.Group(item.ComponentId.ToString()).updateComponentActivity(item);
}
public void ComponentModalClose(ComponentItem item)
{
item.Activity = ComponentActivity.ComponentModalClose;
Clients.Group(item.ComponentId.ToString()).updateComponentActivity(item);
}
public Task JoinGroup(string componentId)
{
return Groups.Add(Context.ConnectionId, componentId);
}
public Task LeaveGroup(string componentId)
{
return Groups.Remove(Context.ConnectionId, componentId);
}
}
so my questions are,
Why am i experiencing a disconnect "WebSocket is closed before the connection is established"
Is my approach the best way to approach this type of requirement?
Use grouping mechanisme of signalr and NOT create multiple connections for your usecase!
There are limitations from IIS and also from browsers. Some browser have a limit of 4 or 5 paralell connections. You can test it by yourself by opening multiple different browsers.
Details about grouping:
Working with groups in signalr is really simple. Details you will find here: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/working-with-groups

$log anonymous function angular js not working

I have a problem when I try to log some data inside the function of webtorrent.
I want to log some values of this.client.add but I don't have access.
Some idea of what's going on here?
import Webtorrent from 'webtorrent';
class PlaylistController {
/** #ngInject */
constructor($http, $log) {
this.log = $log;
this.client = new Webtorrent();
$http
.get('app/playlist/playlist.json')
.then(response => {
this.Torrent = response.data;
});
}
addTorrent(magnetUri) {
this.log.log(magnetUri);
this.client.add(magnetUri, function (torrent) {
// Got torrent metadata!
this.log.log('Client is downloading:', torrent.infoHash);
torrent.files.forEach(file => {
this.log(file);
});
});
this.log.log('sda');
this.log.log(this.client);
}
}
export const playlist = {
templateUrl: "app/playlist/playlist.html",
controller: PlaylistController,
bindings: {
playlist: '<'
}
};
Another thing its I use yeoman for the scaffold of my app and its has JSLint with console.log forbidden and its said that you must use angular.$log, but the thing its I don't wanna change that, I wanna understand the problem here.
You either need to refer to this (the class) as another variable to use inside the function(torrent) function or use arrow functions so that this reference remains the class one.
Solution 1, using another variable to ref the class:
addTorrent(magnetUri) {
this.log.log(magnetUri);
var that = this;
this.client.add(magnetUri, function (torrent) {
// Got torrent metadata!
that.log.log('Client is downloading:', torrent.infoHash);
torrent.files.forEach(file => {
that.log(file);
});
});
this.log.log('sda');
this.log.log(this.client);
}
Solution 2, using arrow functions:
addTorrent(magnetUri) {
this.log.log(magnetUri);
this.client.add(magnetUri, torrent => {
// Got torrent metadata!
this.log.log('Client is downloading:', torrent.infoHash);
torrent.files.forEach(file => {
this.log(file);
});
});
this.log.log('sda');
this.log.log(this.client);
}

Karma testing a lot of files similar in structure automatically

So I have a folder full of scripts that all resemble a structure like this
// Adapter-100.js
angular.module('myModule', ['myParentFactory', function(myParentFactory) {
return angular.extend(myParentFactory, {
"someFunctionA" : function() {},
"someFunctionB" : function() {},
"someFunctionC" : function() {}
});
}]);
And my test just checks that they have these three methods, trouble is there is about 100 of these files (they're adapters for communicating with a server)
Here is a representation of my tests file
// api-adapter-tests.js
describe('Unit: EndPointMethods', function() {
var values, factory, adapter;
// Boot the module
beforeEach(function() {
module('myModule');
inject(function ($injector) {
values = $injector.get('AppConsts');
factory = $injector.get('EndPointConnection');
adapter = $injector.get('TestAdapter'); // This needs to change to match what adapter is being tested
});
});
// Run some tests
describe('AppConsts', function() {
it('Should have an api_host key', function() {
expect(values).toBeDefined();
expect(values.api_host).toBeDefined();
expect(typeof values.api_host).toBe('string');
});
});
// Is this able to be generated to test each adapter independently?
describe('TestEndPointMethod has minimum functional definitions', function() {
it('should have 3 defined functions', function() {
expect(factory.consumeResponse).toBeDefined();
expect(factory.getEndPoint).toBeDefined();
expect(factory.mutator).toBeDefined();
});
});
});
I don't want to have to write a separate describes/it block for each adapter but rather have Karma loop over all of these and create the tests on the fly (the tests are very unlikely to ever change)
I've Googled around for a solution to this but can't seem to find anything that does this kind of thing for me.
You can wrap your suites in a clojure and pass the Adapter you want to test: mocha will take care of running it in the right way - and so Karma.
function runSuiteFor(newAdapter){
return function(){
// api-adapter-tests.js
describe('Unit: EndPointMethods', function() {
var values, factory, adapter;
// Boot the module
beforeEach(function() {
module('myModule');
inject(function ($injector) {
values = $injector.get('AppConsts');
factory = $injector.get('EndPointConnection');
adapter = $injector.get(newAdapter); // set the Adapter here
});
});
// Run some tests
describe('AppConsts', function() {
it('Should have an api_host key', function() {
expect(values).toBeDefined();
expect(values.api_host).toBeDefined();
expect(typeof values.api_host).toBe('string');
});
});
// Is this able to be generated to test each adapter independently?
describe('TestEndPointMethod has minimum functional definitions', function() {
it('should have 3 defined functions', function() {
expect(factory.consumeResponse).toBeDefined();
expect(factory.getEndPoint).toBeDefined();
expect(factory.mutator).toBeDefined();
});
});
});
}
}
var adapters = ['MyTestAdapter1', 'MyTestAdapter2', etc...];
for( var i=0; i<adapters.length; i++){
runSuiteFor(adapters[i])();
}
Note: IE8 has some issues with this approach sometimes, so in case you're with Angular 1.2 bare in mind this.

ember.js Uncaught TypeError: Object data-size has no method 'transitionTo'

I am very new to ember and trying to implement authentication via facebook
I am using ember-facebook.js library to connect with facebook. Once the authentication is successful, I want to transition to some other route e.g. '/index'. This library creates a App.FBUser object in mixin which is populated from the facebook response. The blog say following:
Whenever the user changes (login, logout, app authorization, etc) the method updateFBUser is called, updating the App.FBUser object on your application. You can do whatever you want with this binding, observe it, put it in the DOM, whatever.
Ember.Facebook = Ember.Mixin.create({
FBUser: void 0,
appId: void 0,
fetchPicture: true,
init: function() {
this._super();
return window.FBApp = this;
},
appIdChanged: (function() {
var _this = this;
this.removeObserver('appId');
window.fbAsyncInit = function() {
return _this.fbAsyncInit();
};
return $(function() {
var js;
js = document.createElement('script');
$(js).attr({
id: 'facebook-jssdk',
async: true,
src: "//connect.facebook.net/en_US/all.js"
});
return $('head').append(js);
});
}).observes('appId'),
fbAsyncInit: function() {
var _this = this;
FB.init({
appId: this.get('appId'),
status: true,
cookie: true,
xfbml: true
});
this.set('FBloading', true);
FB.Event.subscribe('auth.authResponseChange', function(response) {
return _this.updateFBUser(response);
});
return FB.getLoginStatus(function(response) {
return _this.updateFBUser(response);
});
},
updateFBUser: function(response) {
console.log("Facebook.updateFBUser: Start");
var _this = this;
if (response.status === 'connected') {
//console.log(_this);
return FB.api('/me', function(user) {
var FBUser;
FBUser = user;
FBUser.accessToken = response.authResponse.accessToken;
if (_this.get('fetchPicture')) {
return FB.api('/me/picture', function(path) {
FBUser.picture = path;
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
});
} else {
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
}
});
} else {
this.set('FBUser', false);
return this.set('FBloading', false);
}
}//updateFBUser
});
Update :
Adding following observer in my LoginController, I am able to capture the App.FBUser update event(it is update after getting response from FB; as indicated by the blog).
From this observer method, when I try to 'transitionTo' my index route I get following error
Uncaught TypeError: Object data-size has no method 'transitionTo'. Following is the code
App.LoginController = Ember.Controller.extend({
onSuccess: (function(){
var self = this;
/*
//tried all these method to redirect but error is the same
var attemptedTransition = this.get('attemptedTransition');
attemptedTransition.retry();
*/
/*
//tried all these method to redirect but error is the same
var router = this.get('target.router');
router.transitionTo('index');
*/
//tried all these method to redirect but error is the same
this.transitionToRoute('index');
}).observes('App.FBUser')
});
Index Route
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition){
var self = this;
if(!App.FBUser){
self.redirectToLogin(transition);
}
},
redirectToLogin: function(transition){
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
}
});
I am not able to get my head around it.
Any help is greatly appreciated. Thanks
How can I access this object in my Route.beforeModel() hook.
Depending on what route's beforModel hook you are talking about, this is how you could do it:
App.SomeRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!Ember.isNone(App.FBUser)) {
// calling 'transitionTo' aborts the transition, redirects to 'index'
this.transitionTo('index');
}
}
});
Update in response to your last comment
The addon you are using is slightly outdated and the proposed implementation method for the mixin in your application will not work with the current version of ember:
App = Ember.Application.create(Ember.Facebook)
App.set('appId', 'yourfacebookappid');
starting from version 1.0.0-rc3 of ember you should rather do it like this:
App = Ember.Application.creatWithMixins(Ember.Facebook);
App.set('appId', 'yourfacebookappid');
After that you should be able to have access to the App.FBUser object as mentioned above.
Update 2
If you want to be able to be notified when some events happend, like login, logout etc. you should (as the Author of the addon states on it's blog post) override the updateFBUser method and do in there your transitions.
Since the addon is trough the mixin available in our App namespace you should be able to do the following:
App = Ember.Application.creatWithMixins(Ember.Facebook, {
updateFBUser: function() {
this._super();
// we are calling super to let the addon
// do it's work but at the same time we get
// notified that something happened, so do at this
// point your transition
}
});
Hope it helps.
As per Issue 1 adding
attributeBindings: [],
to:
return Ember.FacebookView = Ember.View.extend({
solved the issue.

renderAll function does not invoke

Scenario
I am making app using backbonejs, requirejs and jquery. I am retrieving data from remote server. Once the data is fetched, then I want to display it to the user.
Problem
First I fetch data inside app.js file then I make a instance of MoviesView and pass collection in to this view. Inside MoviesView, I have initialize function and inside this function I am listening to an Event triggered by router. Once that event is listened then it should call renderAll function. The problem lies here, it does not invoke renderAll function at all.
My code
here is function where I am fetching data from the server
fetchBoxOfficeMovies: function () {
var movieCollection = new MoviesCollection;
movieCollection.fetch({success: this.successCallback, error: this.errorCallback}).then(function () {
//console.log(movieCollection.toJSON());
new MoviesView({ collection: movieCollection });
});
},
successCallback: function () {
console.log('successCallback');
},
Here is the router where I am triggering an event
routes: {
'': 'index'
},
index: function () {
App.Vent.trigger('init');
console.log('router');
}
And here is initialize and renderAll functions inside MoviesView
initialize: function () {
App.Vent.on('init', this.renderAll, this);
console.log('movies view');
},
renderAll: function () {
console.log('renderAll');
},
Output which I see in my console
Here is what I see in my console
router
successCallback
movies view
As you can see I do not see renderAll in my console.
Question
Why don't I see renderAll and how can I fix this?
UPDATE
Here is my entire App view
var App = Backbone.View.extend({
el: 'body',
initialize: function () {
App.router = new MainRouter();
Backbone.history.start();
this.fetchBoxOfficeMovies();
},
fetchBoxOfficeMovies: function () {
var movieCollection = new MoviesCollection;
movieCollection.fetch({success: this.successCallback, error: this.errorCallback}).then(function () {
//console.log(movieCollection.toJSON());
new MoviesView({ collection: movieCollection });
});
},
successCallback: function () {
console.log('successCallback');
},
errorCallback: function () {
console.log('errorCallback');
}
});
As it can be seen that I am making new instance of MainRouter before calling fetchBoxOfficeMovies, which means I am triggering event before everything else.
As DCoder said, you got the order wrong. Rename your console.log to understand better what's happening.
router -> I trigger a 'init' event
successCallback -> successCallback
movies view -> I start listening 'init' events NOW
Suggested 'Fix':
movieCollection.fetch({
success: this.successCallback,
error: this.errorCallback
}).then(function () {
//console.log(movieCollection.toJSON());
var moviesView = new MoviesView({ collection: movieCollection });
moviesView.renderAll()
});

Categories

Resources