SignalR: getting error: WebSocket closed - asp.net

I am working on project of Ionic with angular and AspNet with SignalR that have chat module.
I use SignalR for Chat.It's working smoothly but some time i am getting error as per below screen shot and because of that it's get stop working at all.
I have hosted my service on IIS and creating proxy and communicating with client and server. Here is sample
(function () {
angular
.module('app')
.factory('SignalRFactory', SignalRFactory);
SignalRFactory.$inject = ['$rootScope', 'Hub', 'ionicToast'];
function SignalRFactory($rootScope, Hub, ionicToast) {
var signalRLocal = this;
var serverURL = 'https://serivcerURL.com/signalr';
//Hub setup
var hub = new Hub('CommunicationHub', {
rootPath: serverURL,
listeners: {
'send': function (data) {
console.log("send " + data);
}
},
errorHandler: function (error) {
//Here i am getting that websocket closed error
console.error(error);
}
});
signalRLocal.Connect = function (user) {
console.log("SignalR Connecting as :" + user.UserName);
hub.invoke('connect', user);
};
return signalRLocal;
}
})();
I have hosted service on IIS. I search for the solution and find something like this link
I also try with above link solution by using "long Polling" as per below
Hub.connection.start({ transport: 'longPolling' });
But i don't want to use "long Polling" at all.
So can someone help me to figure out this issue without use of 'long Polling'.
Can someone tell me what configuration i have to do at client side or at IIS level.

As we said in comments, SignalR client will try to reconnect after the connection is lost. Besides, many factors (such as physical network interruption, client browser failure, server offline etc) can cause the connection lost, this article explains some disconnection scenarios, you can refer to it and find the possible causes of the issue.
Besides, as I mentioned in comment, you can call the Start method from your Closed event handler (disconnected event handler on JavaScript clients) to start a new connection to make client automatically re-establish a connection after it has been lost.
edit:
The connection to ws://localhost:3156/signalr/signalr/connect?transport=webSoc‌​kets&clientProtocol=‌​1.5&connectionToken=‌​g8vpRv9ncVDjPIYB9UuE‌​pAAILEaOcTMTG9p46IA2‌​4 was interrupted while the page was loading.
Under "Client disconnection scenarios" section in the article, you can find:
In a browser client, the SignalR client code that maintains a SignalR connection runs in the JavaScript context of a web page. That's why the SignalR connection has to end when you navigate from one page to another, and that's why you have multiple connections with multiple connection IDs if you connect from multiple browser windows or tabs. When the user closes a browser window or tab, or navigates to a new page or refreshes the page, the SignalR connection immediately ends because SignalR client code handles that browser event for you and calls the Stop method.

I am trying same and my client is in vue.js. I have changed below in vue.config.js
module.exports = {
devServer: {
proxy: {
'/hub': {
target: 'https://localhost:5001',
changeOrigin: false,
secure: false,
headers: {
'x-forwarded-proto': 'http',
},
},
},
},
}
Previously i am trying 'x-forwarded-proto': 'https', when I changed to http its work.
https://localhost:5001 is my .net endpoint and http://localhost:8080 is my vue enpoint
Changed in Startup.csbelow,
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<CardsHub>("/hub/cardsHub");
});
Added below code in vue component
this.connection = new signalR.HubConnectionBuilder()
.withUrl('/hub/cardsHub')
.build()

In my case the culprit was the wrong version of Microsoft.AspNetCore.SignalR.Common
the default one installed was 5.0.3
but I was targeting 3.1.0
Downgrading to 3.1.12 fixed the issue with the connection.

Related

Blazor Application /_blazor/disconnect statuscode 400

my blazor app seems to work but im trying to understand why I'm getting a 400error as seen in the screenshot.
Who can give me a HINT for what Blazor needs technically the "disconnect" ?
SCREENSHOT-disconnect
I'm running everything on a NGINX webserver.
thx Fabio
That call happens here in the code on the window unload event and is used to tell the signalr server that you are disconnecting.
If you are seeing a lot of these it may be that your proxy is not configured correctly for websocket connections.
window.addEventListener(
'unload',
() => {
const data = new FormData();
const circuitId = circuit.circuitId!;
data.append('circuitId', circuitId);
navigator.sendBeacon('_blazor/disconnect', data);
},
false
);
There's a bug reported for this issue here:
https://github.com/dotnet/aspnetcore/issues/30316
Blazor.server.js not respecting configureSignalR builder defined
url/path for disconnnects
As a work-around, I added this to Startup.cs in the Configure method:
var rewriteOptions = new RewriteOptions();
rewriteOptions.AddRewrite("_blazor/disconnect", "/_blazor/disconnect", skipRemainingRules: true);
app.UseRewriter(rewriteOptions);

MSALjs with Single Page App makes debugging impossible

I'm not sure how to explain this succinctly, first part, Implicit flow. I don't exactly understand what implicit flow is. I haven't distilled it down in to a single sentence. Is it a design pattern or a way of handling tokens, both, I dunno.
Working with a pretty basic JavaScript Single Page Application and implementing Msal version 1.2.1. Each time an msalClient gets a token for a scope it leaves behind an iFrame to handle token refreshing.
window.msalConfig = {
auth: {
clientId: '<clientId>'
, authority: "https://login.microsoftonline.com/common"
, validateAuthority: true
}
, cache: {
cacheLocation: "localStorage"
}
, graphScope: {
scopes: ["https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Mail.Send" ]
}
, appScope: {
scopes: ["<clientId>"]
}
, appToken:{
token: null
}
, graphToken:{
token: null
}
};
clientApplication = new Msal.UserAgentApplication(window.msalConfig);
function onSignin(idToken) {
clientApplication.acquireTokenSilent(window.msalConfig.appScope)
.then(function (token) {
window.msalConfig.appToken.token = token;
}, function (error) {
clientApplication.acquireTokenPopup(window.msalConfig.appScope).then(function (token) {
window.msalConfig.appToken.token = token;
}, function (error) {
console.log(error);
});
});
getGraphToken();
};
function getGraphToken() {
clientApplication.acquireTokenSilent(window.msalConfig.graphScope)
.then(function (token) {
window.msalConfig.graphToken.token = token;
}), function (error) {
console.log(error);
};
};
These iFrames just sit here and refresh periodically, presumably getting a new token, or keeping it alive on the other end. (When in the chrome debugger each refresh throws me to the Sources tab, this is makes debugging nearly impossible.)
Also, wondering why cross site cookie errors are being thrown when working with the Graph Scope but not the App Scope.
Curious why this is closed, This still seems to be present.
https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/697
The Implicit Flow is one of the flows for acquiring tokens in OAuth, and is especially well-suited for single-page applications, as it does not require a server component. As you observed, it uses iframes to acquire tokens. You can read more about the Implicit Flow here: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow
The SameSite cookie warnings are expected, as there are some cookies on login.microsoftonline.com have been intentionally left without the SameSite attribute. All of the cookies for login.microsoftonline.com that are needed for authentication have been updated. You can read more here: https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-handle-samesite-cookie-changes-chrome-browser?tabs=dotnet
For issue 697, the main issue of your application being reloaded in a hidden iframe has been addressed in v1.2.0, which now allows you to specify a page without MSAL or any other content (e.g. a blank html page) as your redirect URI, which mitigates the performance overhead of reloading your application in the iframe/popup. You can also set your redirect URI per request, in case you need a different URI that the one set in the configuration.
And if you find iframes difficult to debug, know that we are working a new version of the library which will use the Auth Code Flow w/ PKCE (instead of the Implicit Flow) to acquire tokens, which will use CORS http requests to obtain tokens, instead of iframes. We are planning to have that available this month.

Unknown http error with http request to an iis server with ionic

I've got a problem with my ionic app. I try to get a json object from my iis (asp.NET) server. But the request doesn't even come into my controller. Everytime, i get the same error:
http failure response for (unknown url) 0 unknown error
At first, I thought it was just a cors problem so i fixed it but now, it works with chrome and internet explorer 11 but not with edge and, more important, it doesn't work on device.
I don't think the problem is with my typescript code since there is no problem with a Google request... But, just in case, there is my code:
this.http.get('http://localhost:60196', {withCredentials : true}).subscribe(data => {
this.testData = JSON.stringify(data);
}, err => {
this.testData = JSON.stringify(err);
});
For the record: my iis server use windows impersonation and my app must run on windows devices (i didn't try my code with android or ios devices).
Thank you very much.
it doesn't work on device
this.http.get('http://localhost:60196', {withCredentials : true}).subscribe(data => {
this.testData = JSON.stringify(data);
}, err => {
this.testData = JSON.stringify(err);
});
Get request is rerencing to localhost. Here should be some Api endpoint when testing it on device...

Service Worker - Push notification with VAPID prv/pub keys

A couple of years ago I implemented push notification with service worker on a project I was working on, by registering an app on Firebase, and using the registration number as part of the manifest.json file on the server side app. In that case I requested the user to allow notifications, got the browser registration once, saved on server side, and all works fine.
I'm now trying to implement a similar solution, but using the VAPID (https://developers.google.com/web/ilt/pwa/introduction-to-push-notifications#using_vapid).
Browser registers correctly, sends the registration to the server side app, and the app is able to send push notifications.
The issue I got is that after at least 24 hours, when I try to send a push notification to an already registered subscription, I get InvalidSubscription response (410 NotRegistered).
Using VAPID, does the browser registration expire after a few hours? do I need to get new registration every certain amount of hours? If yes, how? For example, if user never revisits the site within a day or so, how am I able to keep sending them notifications? I can't find any clear reference for this issue I'm experiencing.
Here is the JS code I use within the SW to get the browser registration:
function postPushReg(sub){
var rawKey = sub.getKey ? sub.getKey('p256dh') : '';
var key = rawKey ?
btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) :
'';
var rawAuthSecret = sub.getKey ? sub.getKey('auth') : '';
var authSecret = rawAuthSecret ?
btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) :
'';
fetch('https://XXXXX', {
method: 'post',
headers: {'Content-type': 'application/json'},
body: JSON.stringify({endpoint: sub.endpoint, key: key, authSecret: authSecret}),
});
}
self.addEventListener('install', function(event){
self.registration.pushManager.getSubscription()
.then(function(sub){
if (sub) return postPushReg(sub);
return self.registration.pushManager.subscribe({userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array('XXX')})
.then(function(sub){
postPushReg(sub);
});
});
});
self.addEventListener('push', function(e){
...
});
This is the Rails/Ruby server side gem (webpush) I use to send the notification:
Webpush.payload_send(
message: "msg",
endpoint: j['endpoint'],
p256dh: j['key'],
auth: j['authSecret'],
vapid: {
subject: "mailto:XXXX",
public_key: "XXX",
private_key: "XXX",
}
)
Again, within the first few hours everything works, then I get 410 NotRegistered response.
Trying the same suggestion posted here: Web Push with VAPID: 400/401 Unauthorized Registration , it is now working fine. I get the browser registration only once, and after 2 days it is still working fine

Signalr deserializes my objects incorrectly in IIS 7.5 and Edge/IE, foreverFrame broken?

So I have a simple Signalr/Knockout project that uses the mapping plugin to bind a simple object (item with an array of more items) to viewModels I defined in JS:
var someObjectMapping = {
'MyItemArray': {
create: function (options) {
return new MyItemViewModel(options.data);
}
}
}
var myItemMapping = {
'ItemChildren': {
create: function (options) {
return new ItemChildViewModel(options.data);
}
}
}
var SomeObjectViewModel = function (data) {
ko.mapping.fromJS(data, someObjectMapping, this);
}
var MyItemViewModel = function (data) {
ko.mapping.fromJS(data, myItemMapping, this);
}
var ItemChildViewModel = function (data) {
ko.mapping.fromJS(data, null, this);
}
I use SignalR's default settings to connect to my hub like so:
var myHubProxy = $.connection.myHub;
myHubProxy.client.processSomeObject = function(someObject) {
console.log('SomeObject received');
var viewModel = new SomeObjectViewModel(someObject);
ko.applyBindings(viewModel);
}
$.connection.hub.start().done(function() {
console.log('Now connected, connection ID=' + $.connection.hub.id);
myHubProxy.server.getSomeObject();
});
When my object comes back, knockout applies the binding and the mapping gets processed. Then the object and its child arrays are naturally rendered on the page:
<h2 data-bind="text: MyItem"></h2>
<ul data-bind="foreach: MyItemArray">
<li>
<span data-bind="text: Name"></span>
<ul data-bind="foreach: ItemChildren">
<li data-bind="text: Name"></li>
</ul>
</li>
</ul>
Now for the kicker: This works in on my local machine (Win 10, IIS Express), in all browsers (Chrome/Firefox/Safari/IE), no problem. When I publish this to IIS 7.5 however, it works in all browsers except for Internet Explorer 8-10 and Microsoft Edge. Same code.
When I drill through F12 I notice that, from IIS, the create function in the first mapping gets an array:
When instead, on first break, I should have the first item in my array like it does on my local machine:
Drilling up the callstack reveals that the parent object in knockout.mapping's createCallback function is not being interpreted as an array as it should:
However, on my local machine, it works as expected:
Strangely, one of two things can workaround the issue: First, if I serialize the object I get back from SignalR and then deseralize it before binding it my knockout model, everything works from all browsers from IIS 7.5:
myHubProxy.client.processSomeObject = function(someObject) {
console.log('SomeObject received');
var jsonStr = JSON.stringify(someObject);
var viewModel = new SomeObjectViewModel(JSON.parse(jsonStr));
ko.applyBindings(viewModel);
}
But this can affect performance.
OR if I force SignalR's transport to longPolling, again everything works in all browsers from IIS 7.5:
$.connection.hub.start({ transport: "longPolling" }).done(function () {
console.log('Now connected, connection ID=' + $.connection.hub.id);
myHubProxy.server.getSomeObject();
});
But then I wouldn't have the advantages WebSockets provides over polling.
IIS 7.5 doesn't support WebSockets
I can also hardcode my json and bind it with knockout, which works as expected in all browsers.
It took me forever to discover what was going on, and I've been struggling to figure this one out. It's strange that this works from IIS 7.5 in all the other browsers but IE/Edge when they're running the same simple script. It also works with in all browsers IIS 10 (non-Express), which isn't an option for the server I'm publishing to.
Edit: Uffe has pointed out that IIS 7.5 doesn't support WebSockets. After enabling logging, I saw that, for IIS 7.5, Signalr will instead fallback to foreverFrame for IE and serverSentEvents (which isn't supported in IE) for other browsers.
I also tested forcing foreverFrame, which reproduced the issue on my machine with IIS 10 Express:
$.connection.hub.start({ transport: 'foreverFrame'}).done(function () {
console.log('Now connected, connection ID=' + $.connection.hub.id);
myHubProxy.server.getSomeObject();
});
So another workaround would be to skip foreverFrame from the transport altogether when publishing to IIS 7.5 like so:
$.connection.hub.start({ transport: ['serverSentEvents','longPolling']}).done(function () {
console.log('Now connected, connection ID=' + $.connection.hub.id);
myHubProxy.server.getSomeObject();
});
Here's a sample project which reproduces the issue:
https://onedrive.live.com/redir?resid=D4E23CA0ED671323!1466815&authkey=!AEAEBajrZx3y8e4&ithint=folder%2csln
The problem is that using ForeverFrame, the JSON parsing is done in another frame, and the resulted array is not instanceof Array in the current frame, as described here.
In order to bypass that, SignalR (starting from 2.1.0) allows you to supply your own JSON parser to ForeverFrame:
$.connection.hub.json = {
parse: function(text, reviver) {
console.log("Parsing JSON");
return window.JSON.parse(text, reviver);
},
stringify: function(value, replacer, space) {
return window.JSON.stringify(value, replacer, space);
}
};
By calling window.JSON.parse, you ensure that the array will be parsed correctly.
You will never get WebSockets with SignalR on IIS 7.5
See the docs - You will need win8/2012 Server and IIS8
Edit: Sorry for not answering the stuff about serialization, but since you mention that you want websockets I thought it was important to mention this...

Resources