SignalR connection does not work in angular - asp.net

I had an old backend structure. When i research, i always encounter with signalr with .net core. But i had .net structure and there is no code in the Internet. Could anyone help me to solve this problem?
Here is my code:
private hubConnection: HubConnection;
private hubUrl: string = `${environment.hubUrl}`;
constructor() { }
ngOnInit(): void {
this.startHubConnection();
}
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
startHubConnection() {
// #ts-ignore
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.hubUrl,
// {
// // eslint-disable-next-line #typescript-eslint/naming-convention
// // httpClient : { 'Access-Control-Allow-Origin': '*'},
// headers: {
// // eslint-disable-next-line #typescript-eslint/naming-convention
// 'Access-Control-Allow-Origin': '*',
// },
// }
)
// .withAutomaticReconnect()
.configureLogging(LogLevel.Information)
.build();
this.hubConnection.start().then().catch(err => console.log('hata yeri '+err));
this.hubConnection.on('SicaklikHub', (response: any) => {
const resp = JSON.parse(response);
console.log(resp);
});
}
This is my output right now:
Access to fetch at 'http://domain:port/signalr/hubs/negotiate?negotiateVersion=1' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Related

It's possible to get next.js request object globally?

I am using fastify with next.js and I need to include tracing (requestId is the problem so far). What I am doing right now is creating a fastify onRequest hook and generating a requestId value and setting it in request object (could be as a request header as well). What I want is to get access to this request object for two reasons:
In logger object (pino in this case, I want to include the requestId in all custom server-side logs).
In all request that needs to be made to other services need to include the requestId in headers.
Maybe I am missing something trivial and I'm not doing it the best way.
HERE SOME SNIPPETS
This how I am generating the reqId
const fastify = fastifyFactory({
logger, // logger configuration (Pino instance with custom configuration, see below)
genReqId: () => {
return Math.random()
.toString(36)
.slice(-6);
}
});
pino instance
const pino = require('pino');
const logger = pino({
messageKey: 'message',
prettyPrint: true,
changeLevelName: 'severity',
useLevelLabels: true,
base: {
serviceContext: {
service: 'web'
}
},
level:'info'
});
module.exports = {
logger
};
This is a plugin to gets the reqId generated and setting it to a query property within request object
const tracing = function tracing(fastify, opt, next) {
fastify.addHook('onRequest', (req, res, nextRequest) => {
const { id } = req;
const logger = fastify.log.child({ reqId: id });
req.query.reqId = id;
fastify.log = logger; //overrides the current fastify logger to include the reqId in all custom logs
nextRequest();
});
next();
};
tracing[Symbol.for('skip-override')] = true;
module.exports = tracing;
I have no problem when using fastify.log.info(...) because how logger is overrided in each request, it will include the reqId as a child log. The problem is that I want to create a generic logger to use at any part and fastify logger is not available in React components (for example to write logs at getInitialProps). Another important think is tha I need to include this reqId in all request I send to other services (ex: when fetching data), this is why I tried to store this value in request object but need to get it.
Starting from a project build with:
npx create-next-app --example custom-server-fastify custom-server-fastify-app
And changing the server.js with:
const Next = require('next')
const Fastify = require('fastify')
// your pino config
const fastify = Fastify({
logger: {
level: 'info',
prettyPrint: true,
changeLevelName: 'severity',
useLevelLabels: true,
base: {
serviceContext: {
service: 'web'
}
}
},
genReqId: () => { return Math.random().toString(36).slice(-6) }
})
// your plugin
const aPlugin = function yourPlugin (fastify, opts, next) {
fastify.addHook('onRequest', (request, reply, next) => {
request.log.info('hello')
const { id } = request
request.query.reqId = id
next()
})
next()
}
aPlugin[Symbol.for('skip-override')] = true
fastify.register(aPlugin)
[.... other generated code]
const port = parseInt(process.env.PORT, 10) || 3000
[.... other generated code]
fastify.get('/*', (req, reply) => {
console.log('-------->', req.id, req.query.reqId) // both your id is ok
return app.handleRequest(req.req, reply.res).then(() => {
reply.sent = true
})
[.... other generated code]
})
Then:
npm run dev
# another console
curl http://localhost:3000/
It will print out:
[1558441374784] INFO : Server listening at http://127.0.0.1:3000
serviceContext: {
"service": "web"
}
> Ready on http://localhost:3000
[1558441405416] INFO : incoming request
serviceContext: {
"service": "web"
}
reqId: "2i810l"
req: {
"method": "GET",
"url": "/",
"hostname": "localhost:3000",
"remoteAddress": "127.0.0.1",
"remotePort": 57863
}
req id ----> 2i810l
--------> 2i810l 2i810l
[ event ] build page: /
[ wait ] compiling ...
[1558441406171] INFO : request completed
serviceContext: {
"service": "web"
}
reqId: "2i810l"
res: {
"statusCode": 200
}
responseTime: 753.012099981308
So I think the misunderstanding is in the request object that is the Fastify request and not the Node.js "low level" request object, that could be accessed with request.req.
Moreover, running fastify.log = logger; is dangerous because it means that each request override and create a new logger and change the logger for the fastify instance, this is not safe, and as shown it is not necessary.
If you want more child logger (per route prefix per example) I suggest exploring/using the onRegister hook.
EDIT:
Now the custom hook print:
[1558443540483] INFO : hello
serviceContext: {
"service": "web"
}
reqId: "zjuhw2"

How to pass Custom Header from React JS client to SignalR hub?

I am setting up a new SignalR react app ("#aspnet/signalr") with Dot Net Core 2.0. I want to send custom headers to SignalR hub "negotiate" request (like request.headers["MyHeader"] = "Header").
I am able to connect to hub and get data back to react app. I have tried setting custom header by trying to overwrite httpClient in options passed to "withUrl".
With the code provided here I am getting error: "Error: Failed to complete negotiation with the server: Error: Unexpected status code returned from negotiate undefined"
It connects when httpClient is removed from options.
import { HubConnectionBuilder } from '#aspnet/signalr';
const options = {
accessTokenFactory: () => {
return "jwt token";
},
httpClient: {
post: (url, httpOptions) => {
httpOptions.headers = {
...httpOptions.headers,
MyHeader: "NewHeader"
};
httpOptions.method = "POST";
httpOptions.url = url;
return httpOptions;
}
}
};
const connection = new HubConnectionBuilder()
.withUrl("https://localhost:5001/chatHub", options)
.build();
connection.start().catch(function(err) {
console.log("Error on Start : ", err);
});
The way I see header as "Authorize": "jwt token", I expect to see another header in "https://localhost:5001/chatHub/negotiate" request as "MyHeader": "NewHeader"
Found answer to this.
httpClient.post overwrites the response of default SignalR httpClient.post.
Below update to httpClient worked.
httpClient: {
post: (url, httpOptions) => {
const headers = {
...httpOptions.headers,
MyHeader: "MyHeader"
};
return axios.post(url, {}, { headers }).then(response => {
return (newResponse = {
statusCode: response.status,
statusText: response.statusText,
content: JSON.stringify(response.data)
});
});
}
}
SignalR "negotiate" expects response in this form.
{
statusCode: 200,
statusText: "ok",
content: "<string response>"
}

Angular5 Response Header (Content-Disposition) Reading

How Can I read Response Header (Content-Disposition)? Please share resolution.
When I check at either Postman or Google Chrome Network tab, I can see 'Content-Disposition' at the response headers section for the HTTP call, but NOT able to read the header parameter at Angular Code.
// Node - Server JS
app.get('/download', function (req, res) {
var file = __dirname + '/db.json';
res.set({
'Content-Type': 'text/plain',
'Content-Disposition': 'attachment; filename=' + req.body.filename
})
res.download(file); // Set disposition and send it.
});
// Angular5 Code
saveFile() {
const headers = new Headers();
headers.append('Accept', 'text/plain');
this.http.get('http://localhost:8090/download', { headers: headers })
.subscribe(
(response => this.saveToFileSystem(response))
);
}
private saveToFileSystem(response) {
const contentDispositionHeader: string = response.headers.get('Content-Disposition'); // <== Getting error here, Not able to read Response Headers
const parts: string[] = contentDispositionHeader.split(';');
const filename = parts[1].split('=')[1];
const blob = new Blob([response._body], { type: 'text/plain' });
saveAs(blob, filename);
}
I have found the solution to this issue. As per Access-Control-Expose-Headers, only default headers would be exposed.
In order to expose 'Content-Disposition', we need to set 'Access-Control-Expose-Headers' header property to either '*' (allow all) or 'Content-Disposition'.
// Node - Server JS
app.get('/download', function (req, res) {
var file = __dirname + '/db.json';
res.set({
'Content-Type': 'text/plain',
'Content-Disposition': 'attachment; filename=' + req.body.filename,
'Access-Control-Expose-Headers': 'Content-Disposition' // <== ** Solution **
})
res.download(file); // Set disposition and send it.
});
It is not the problem with Angular, is the problem with CORS.
If the server does not explicitly allow your code to read the headers, the browser don't allow to read them.
In the server you must add Access-Control-Expose-Headers in the response.
In the response it will be like Access-Control-Expose-Headers:<header_name>,
In asp.net core it can be added while setting up CORS in ConfigureServices method in startup.cs
this solution help me to get the Content-Disposition from response header.
(data)=>{ //the 'data' is response of file data with responseType: ResponseContentType.Blob.
let contentDisposition = data.headers.get('content-disposition');
}
Firstly you need to allow your server to expose these headers. Note that it will show in you browser network tab, regardless if you have these settings. This makes it 'available'.
With C# it would look something like this:
services.AddCors(options => {
options.AddPolicy(AllowSpecificOrigins,
builder => {
builder
.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("Content-Disposition", "downloadFileName");
});
});
When you send your API request to the server ensure that you include the "observe" in you return. See below:
getFile(path: string): Observable<any> {
// Create headers
let headers = new HttpHeaders();
// Create and return request
return this.http.get<Blob>(
`${environment.api_url}${path}`,
{ headers, observe: 'response', responseType: 'blob' as 'json' }
).pipe();
}
Then in your response of your angular on your subscribe you can access your filename like this (the subscribe method is not complete it attaches to a pipe function)
.....
.subscribe((response: HttpResponse<Blob>) => {
const fileName = response.headers.get('content-disposition')
.split(';')[1]
.split('filename')[1]
.split('=')[1]
.trim();
});

Angular 4 and ASP.Net MVC 5 : returns an Empty JSON in response

after merging angular app with asp.net MVC calling API from angular returns an empty JSON.
The angular and asp.net are in the same domain.
If I call the API With PostMan, I have a JSON with the result. but if I call it in the angular app my JSON result is empty.
Are there any tips for communicating angular app with asp.net MVC after merging and serving in the same domain?
Update 1:
The code that used to calling Webservice:
getSheets(): Observable<Sheet[]> {
return this.http.get(this.config.apiUrl + '/api/SheetsRelationAPI',
this.jwt())
.map(this.extractData)
.do(data => console.log('SheetsData:', data)) // debug
.catch(this.handleError);
}
/**
* Handle HTTP error
*/
private handleError(error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
const errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
// private helper methods
private jwt() {
// create authorization header with jwt token
const currentUser = JSON.parse(atob(this.cookie.getCookie('currentUser')));
if (currentUser && currentUser.access_token) {
const headers = new Headers({ 'Authorization': 'Bearer ' + currentUser.access_token},
);
return new RequestOptions({ headers: headers });
}
}
private extractData(res: Response) {
const body = res.json();
return body || [];
}
Update 2:
I notice that my API if I called it from outside domain it respond 2 times:
inspecting network with google chrome inspect element:
the first response is "zone.js" initiator and the second response is an "other" initiator
If I call the API from inside of the Domain I just have a response from "zone.js" initiator and it returns an empty JSON.
Update 3
export class OtherComponent implements OnInit {
sheets: Sheet[] = [];
errorMessage: string;
constructor(private httpService: HttpService) {
// this.sheets = this.ichartHttp.getSheets();
// console.log(this.sheets);
}
getSheets() {
this.httpService.getSheets()
.subscribe(
sheets => this.sheets = sheets,
error => this.errorMessage = <any>error
);
}
ngOnInit() {
this.getSheets();
}
}
The Problem is with my Authentication methods,
I use two types of authentication, MVC and WebAPI they conflict if I send a request to API under the same Domain.
So my Answer is: Your Angular Code looks good, take a look at your middleware project

How to recognise an existing Google OAuth authentication made via Firebase and perform a Google Directory API request in Angular 2?

I want to use my existing authentication and be able to use that same authentication to perform a get request to the Google Directory API. Here's my current code:
login() {
this.firebaseRef = new Firebase('https://xxx.firebaseio.com');
this.firebaseRef.authWithOAuthPopup("google", (error, authData) => {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
}, {
scope: "https://www.googleapis.com/auth/admin.directory.user.readonly"
});
}
getData() {
// TO-DO
// Recognise existing OAuth and perform a GET request to
// https://www.googleapis.com/admin/directory/v1/users?domain=nunoarruda.com
}
You could leverage the getAuth method on the firebaseRef instance. Something like that:
getData() {
var authData = this.firebaseRef.getData();
var provider = authData.provider;
// In your case provider contains "google"
}
See this documentation: https://www.firebase.com/docs/web/api/firebase/getauth.html.
I've found the solution. I need to use the current access token in the http request headers for the GET request.
import {Http, Headers} from 'angular2/http';
getData() {
// get access token
let authData = this.firebaseRef.getAuth();
let oauthToken = authData.google.accessToken;
console.log(oauthToken);
// set authorization on request headers
let headers = new Headers();
headers.append('Authorization', 'Bearer ' + oauthToken);
// api request
this.http.get('https://www.googleapis.com/admin/directory/v1/users?domain=nunoarruda.com',{headers: headers}).subscribe((res) => {
console.log(res.json());
});
}

Resources