This is a strange one, but here's the situation.
I'm using Next.js with the Next-auth package to handle authentication.
I'm not using Server-Side rendering, it's an admin area, so there is no need for SSR, and in order to authenticate users, I've created a HOC to wrap basically all components except for the "/sign-in" route.
This HOC all does is check if there's a session and then adds the "access token" to the Axios instance in order to use it for all async calls, and if there is no session, it redirects the user to the "sign-in" page like this ...
const AllowAuthenticated = (Component: any) => {
const AuthenticatedComponent = () => {
const { data: session, status }: any = useSession();
const router = useRouter();
useEffect(() => {
if (status !== "loading" && status === "unauthenticated") {
axiosInstance.defaults.headers.common["Authorization"] = null;
signOut({ redirect: false });
router.push("/signin");
} else if (session) {
axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${session.accessToken.accessToken}`;
}
}, [session, status]);
if (status === "loading" || status === "unauthenticated") {
return <LoadingSpinner />;
} else {
return <Component />;
}
};
return AuthenticatedComponent;
};
export default AllowAuthenticated;
And in the Axios instance, I'm checking if the response is "401", then I log out the user and send him to the "sign-in" screen, like this ...
axiosInstance.interceptors.response.use(
response => response,
error => {
const { status } = error.response;
if (status === 401) {
axiosInstance.defaults.headers.common["Authorization"] = null;
signOut({ redirect: false });
return Promise.reject(error);
}
return Promise.reject(error);
},
);
Very simple stuff, and it works like a charm until I decided to upgrade my project to use "react 18.1.0" and "react-dom 18.1.0", then all of a sudden, my API calls doesn't get the "Authorization" header and they return "401" and the user gets logged out :(
If I tried to make an API call inside the HOC right after I set the Auth headers it works, sot I DO get the "token" from the session, but all the async dispatch calls inside the wrapped component return 401.
I forgot to mention, that this issue happens on page refresh, if I didn't refresh the page after I sign in, everything works great, but once I refresh the page the inner async dispatch calls return 401.
I Updated all the packages in my project including Axios and next-auth, but it didn't help.
I eventually had to downgrade back to "react 17.0.2" and everything works again.
Any help is much appreciated.
For those of you who might come across the same issue.
I managed to solve this by not including the logic for adding the token to the "Authorization" header inside the HOC, instead, I used a solution by #kamal-choudhary on a post on Github talking about how to add "JWT" to every axios call using next-auth.
Using #jaketoolson help at that Github post, he was able to attach the token to every "Axios" call.
The solution is basically to create an Axios instance and add an interceptor like I was doing above, but not just for the response, but also for request.
You'll add an interceptor for every request and check if there's a session, and then attach the JWT to the Authorization header.
That managed to solve my issue, and now next-auth works nicely with react 18.
Here's the code he's using ...
import axios from 'axios';
import { getSession } from 'next-auth/react';
const baseURL = process.env.SOME_API_URL || 'http://localhost:1337';
const ApiClient = () => {
const defaultOptions = {
baseURL,
};
const instance = axios.create(defaultOptions);
instance.interceptors.request.use(async (request) => {
const session = await getSession();
if (session) {
request.headers.Authorization = `Bearer ${session.jwt}`;
}
return request;
});
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.log(`error`, error);
},
);
return instance;
};
export default ApiClient();
Don't forget to give them a thumbs up for their help if it works for you ...
https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1993281
https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1898233
I am building out a webpage which needs to make a call to the Google Geocoder api.
In order to hide the api key from public view, I am trying to set up server middleware to act as a REST api endpoint.
I have checked through all of the documentation and copied all of it, but the response is always the same. I receive the entirety of the html body back from the axios request rather than anything else I send back via express.
In my component I have the following code:
computed: {
normalizedAddress() {
return `${this.member.address.street} ${this.member.address.city}, ${this.member.address.state} ${this.member.address.zip}`.replace(
/\s/g,
'+'
)
}
},
methods: {
async getLocation() {
try {
const res = await axios.get(
`/api/geocode/${this.normalizedAddress}`
)
console.log(res)
} catch (err) {
console.log(err)
}
}
},
In nuxt.config.js I have this setup
serverMiddleware: ['~/api/geocode.js'],
In the root of my project I have an api folder with geocode.js stored there.
geocode.js is below
import express from 'express';
import axios from "axios";
let GEO_API = "MY_API_KEY"
const app = express();
app.use(express.json());
app.get("/", async (req, res) => {
const uri = `https://maps.googleapis.com/maps/api/geocode/json?address=${req.params.address}&key=${GEO_API}`
try {
const code = await axios.get(uri);
if (code.status !== "OK") {
return res.status(500).send(code.status)
}
return res.status(200).send(code);
} catch (err) {
return res.status(500).send(err);
}
});
export default {
path: "/api/geocode/:address",
handler: app
}
Again. The response always has the entire html document from the website sent back (some 100 pages of code).
Even when I set the response to fixed text, that is not sent.
The only detail I can think of that might be interrupting it is that I have my own custom routing setup using the #nuxtjs/router build module in use.
I'm trying to instrument my React Web App using https://docs.aws.amazon.com/xray/latest/devguide/scorekeep-client.html
I am using axios interceptor,But unable to instrument any further ideas?
Here's the axois interceptor code you'll need for X-Ray. Axios does not use the base HTTP library from Node, so you'll need to include this patcher.
I recently wrote a sample app to be published, here is the snippet I used.
Hopefully this helps.
const xray = require('aws-xray-sdk-core');
const segmentUtils = xray.SegmentUtils;
let captureAxios = function(axios) {
//add a request interceptor on POST
axios.interceptors.request.use(function (config) {
var parent = xray.getSegment();
var subsegment = parent.addNewSubsegment(config.baseURL + config.url.substr(1));
subsegment.namespace = 'remote';
let root = parent.segment ? parent.segment : parent;
let header = 'Root=' + root.trace_id + ';Parent=' + subsegment.id + ';Sampled=' + (!root.notTraced ? '1' : '0');
config.headers.get={ 'x-amzn-trace-id': header };
config.headers.post={ 'x-amzn-trace-id': header };
xray.setSegment(subsegment);
return config;
}, function (error) {
var subsegment = xray.getSegment().addNewSubsegment("Intercept request error");
subsegment.close(error);
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
var subsegment = xray.getSegment();
const res = { statusCode: response.status, headers: response.headers };
subsegment.addRemoteRequestData(response.request, res, true);
subsegment.close();
return response;
}, function (error) {
var subsegment = xray.getSegment();
subsegment.close(error);
return Promise.reject(error);
});
};
module.exports = captureAxios;
Usage
Just pass in an initialized instance of Axios.
For React, you'll have to tell me a bit more about what your setup is, and what you're trying to accomplish. X-Ray only cares about the routes in your application - typically interceptors are setup on the routes to collect data and create (and close) the root segment (see the X-Ray SDK for Node Express here). For browser-based integration, we're still discussing possible options from the X-Ray end.
I'm working with express and mongoose, having problem.
As an API server, I implemented a router js file. One of posts is to save four objects in db at once.
As I know, mongoose query function like save() can be used as a promise object.
So I attended to make a mongoose query array, and put it into Promise.all as an argument, but It doesn't work. (I can't find any records on db after run this code.)
following is my code, please review it and teach me why it doesn't work.
import { Router } from 'express';
import Collection from '../models/collection.model';
const router = new Router();
router.post('/api/collections/basic-pick/:userId', (req, res) => {
const pickedMons = req.body.pickedMons;
const collections = [];
let condition = 1;
for (const mon of pickedMons) {
condition = Math.floor((Math.random() * 5) + 1);
const collection = new Collection({
_monId: mon._id,
_userId: req.params.userId,
condition,
});
// this log prints right objects
console.log('collection: ' + collection);
collections.push(collection);
}
const proms = [];
for (const collection of collections) {
// collection.save() function returns promise, right?
proms.push(collection.save);
}
Promise.all(proms).then(() => {
return res.json({ success: true });
});
});
export default router;
I'm learning Angular 2 Beta. I wonder how to download the PDF file from the API and display it in my view? I've tried to make a request using the following:
var headers = new Headers();
headers.append('Accept', 'application/pdf');
var options = new ResponseOptions({
headers: headers
});
var response = new Response(options);
this.http.get(this.setUrl(endpoint), response).map(res => res.arrayBuffer()).subscribe(r=>{
console.log(r);
})
Please note that I only use the console.log to see the value of r
But I always get the following exception message:
"arrayBuffer()" method not implemented on Response superclass
Is it because that method isn't ready yet in Angular 2 Beta? Or is there any mistake that I made?
Any help would be appreciated. Thank you very much.
In fact, this feature isn't implemented yet in the HTTP support.
As a workaround, you need to extend the BrowserXhr class of Angular2 as described below to set the responseType to blob on the underlying xhr object:
import {Injectable} from 'angular2/core';
import {BrowserXhr} from 'angular2/http';
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor() {}
build(): any {
let xhr = super.build();
xhr.responseType = "blob";
return <any>(xhr);
}
}
Then you need to wrap the response payload into a Blob object and use the FileSaver library to open the download dialog:
downloadFile() {
this.http.get(
'https://mapapi.apispark.net/v1/images/Granizo.pdf').subscribe(
(response) => {
var mediaType = 'application/pdf';
var blob = new Blob([response._body], {type: mediaType});
var filename = 'test.pdf';
saveAs(blob, filename);
});
}
The FileSaver library must be included into your HTML file:
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js"></script>
See this plunkr: http://plnkr.co/edit/tfpS9k2YOO1bMgXBky5Y?p=preview
Unfortunately this will set the responseType for all AJAX requests. To be able to set the value of this property, there are more updates to do in the XHRConnection and Http classes.
As references see these links:
Download pdf file using jquery ajax
Receive zip file, angularJs
Edit
After thinking a bit more, I think that you could leverage hierarchical injectors and configure this provider only at the level of the component that executes the download:
#Component({
selector: 'download',
template: '<div (click)="downloadFile() ">Download</div>'
, providers: [
provide(CustomBrowserXhr,
{ useClass: CustomBrowserXhr }
]
})
export class DownloadComponent {
#Input()
filename:string;
constructor(private http:Http) {
}
downloadFile() {
this.http.get(
'https://mapapi.apispark.net/v1/images/'+this.filename).subscribe(
(response) => {
var mediaType = 'application/pdf';
var blob = new Blob([response._body], {type: mediaType});
var filename = 'test.pdf';
saveAs(blob, filename);
});
}
}
This override would only applies for this component (don't forget to remove the corresponding provide when bootstrapping your application). The download component could be used like that:
#Component({
selector: 'somecomponent',
template: `
<download filename="'Granizo.pdf'"></download>
`
, directives: [ DownloadComponent ]
})
So here is how I managed to get it to work.
My situation: I needed to download a PDF from my API endpoint, and save the result as a PDF in the browser.
To support file-saving in all browsers, I used the FileSaver.js module.
I created a component that takes the ID of the file to download as parameter.
The component, , is called like this:
<pdf-downloader no="24234232"></pdf-downloader>
The component itself uses XHR to fetch/save the file with the number given in the no parameter. This way we can circumvent the fact that the Angular2 http module doesn't yet support binary result types.
And now, without further ado, the component code:
import {Component,Input } from 'angular2/core';
import {BrowserXhr} from 'angular2/http';
// Use Filesaver.js to save binary to file
// https://github.com/eligrey/FileSaver.js/
let fileSaver = require('filesaver.js');
#Component({
selector: 'pdf-downloader',
template: `
<button
class="btn btn-secondary-outline btn-sm "
(click)="download()">
<span class="fa fa-download" *ngIf="!pending"></span>
<span class="fa fa-refresh fa-spin" *ngIf="pending"></span>
</button>
`
})
export class PdfDownloader {
#Input() no: any;
public pending:boolean = false;
constructor() {}
public download() {
// Xhr creates new context so we need to create reference to this
let self = this;
// Status flag used in the template.
this.pending = true;
// Create the Xhr request object
let xhr = new XMLHttpRequest();
let url = `/api/pdf/iticket/${this.no}?lang=en`;
xhr.open('GET', url, true);
xhr.responseType = 'blob';
// Xhr callback when we get a result back
// We are not using arrow function because we need the 'this' context
xhr.onreadystatechange = function() {
// We use setTimeout to trigger change detection in Zones
setTimeout( () => { self.pending = false; }, 0);
// If we get an HTTP status OK (200), save the file using fileSaver
if(xhr.readyState === 4 && xhr.status === 200) {
var blob = new Blob([this.response], {type: 'application/pdf'});
fileSaver.saveAs(blob, 'Report.pdf');
}
};
// Start the Ajax request
xhr.send();
}
}
I've used Font Awesome for the fonts used in the template. I wanted the component to display a download button and a spinner while the pdf is fetched.
Also, notice I could use require to fetch the fileSaver.js module. This is because I'm using WebPack so I can require/import like I want. Your syntax might be different depending of your build tool.
I don't think all of these hacks are necessary. I just did a quick test with the standard http service in angular 2.0, and it worked as expected.
/* generic download mechanism */
public download(url: string, data: Object = null): Observable<Response> {
//if custom headers are required, add them here
let headers = new Headers();
//add search parameters, if any
let params = new URLSearchParams();
if (data) {
for (let key in data) {
params.set(key, data[key]);
}
}
//create an instance of requestOptions
let requestOptions = new RequestOptions({
headers: headers,
search: params
});
//any other requestOptions
requestOptions.method = RequestMethod.Get;
requestOptions.url = url;
requestOptions.responseType = ResponseContentType.Blob;
//create a generic request object with the above requestOptions
let request = new Request(requestOptions);
//get the file
return this.http.request(request)
.catch(err => {
/* handle errors */
});
}
/* downloads a csv report file generated on the server based on search criteria specified. Save using fileSaver.js. */
downloadSomethingSpecifc(searchCriteria: SearchCriteria): void {
download(this.url, searchCriteria)
.subscribe(
response => {
let file = response.blob();
console.log(file.size + " bytes file downloaded. File type: ", file.type);
saveAs(file, 'myCSV_Report.csv');
},
error => { /* handle errors */ }
);
}
Here is the simplest way to download a file from an API that I was able to come up with.
import { Injectable } from '#angular/core';
import { Http, ResponseContentType } from "#angular/http";
import * as FileSaver from 'file-saver';
#Injectable()
export class FileDownloadService {
constructor(private http: Http) { }
downloadFile(api: string, fileName: string) {
this.http.get(api, { responseType: 'blob' })
.subscribe((file: Blob) => {
FileSaver.saveAs(file, fileName);
});
}
}
Call the downloadFile(api,fileName) method from your component class.
To get FileSaver run the following commands in your terminal
npm install file-saver --save
npm install #types/file-saver --save
Hello, here is a working example. It is also suitable for PDF!
application/octet-stream - general type.
Controller:
public FileResult exportExcelTest()
{
var contentType = "application/octet-stream";
HttpContext.Response.ContentType = contentType;
RealisationsReportExcell reportExcell = new RealisationsReportExcell();
byte[] filedata = reportExcell.RunSample1();
FileContentResult result = new FileContentResult(filedata, contentType)
{
FileDownloadName = "report.xlsx"
};
return result;
}
Angular2:
Service xhr:
import { Injectable } from '#angular/core';
import { BrowserXhr } from '#angular/http';
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor() {
super();
}
public build(): any {
let xhr = super.build();
xhr.responseType = "blob";
return <any>(xhr);
}
}
Install file-saver npm packages "file-saver": "^1.3.3", "#types/file-saver": "0.0.0" and include in vendor.ts import 'file-saver';
Component btn download.
import { Component, OnInit, Input } from "#angular/core";
import { Http, ResponseContentType } from '#angular/http';
import { CustomBrowserXhr } from '../services/customBrowserXhr.service';
import * as FileSaver from 'file-saver';
#Component({
selector: 'download-btn',
template: '<button type="button" (click)="downloadFile()">Download</button>',
providers: [
{ provide: CustomBrowserXhr, useClass: CustomBrowserXhr }
]
})
export class DownloadComponent {
#Input() api: string;
constructor(private http: Http) {
}
public downloadFile() {
return this.http.get(this.api, { responseType: ResponseContentType.Blob })
.subscribe(
(res: any) =>
{
let blob = res.blob();
let filename = 'report.xlsx';
FileSaver.saveAs(blob, filename);
}
);
}
}
Using
<download-btn api="api/realisations/realisationsExcel"></download-btn>
To get Filesaver working in Angular 5: Install
npm install file-saver --save
npm install #types/file-saver --save
In your component use import * as FileSaver from "file-saver";
and use FileSaver.default and not FileSaver.SaveAs
.subscribe(data => {
const blob = data.data;
const filename = "filename.txt";
FileSaver.default(blob, filename);
Here is the code that works for downloadign the API respone in IE and chrome/safari. Here response variable is API response.
Note: http call from client needs to support blob response.
let blob = new Blob([response], {type: 'application/pdf'});
let fileUrl = window.URL.createObjectURL(blob);
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileUrl.split(':')[1] + '.pdf');
} else {
window.open(fileUrl);
}
Working solution with C# Web API loading PDF as a byte array:
C# loads PDF as a byte array and converts to Base64 encoded string
public HttpResponseMessage GetPdf(Guid id)
{
byte[] file = GetFile(id);
HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StringContent("data:application/pdf;base64," + Convert.ToBase64String(file));
return result;
}
Angular service gets PDF
getPdf(): Observable<string> {
return this.http.get(webApiRequest).pipe(
map(response => {
var anonymous = <any>response;
return anonymous._body;
})
);
}
Component view embeds the PDF via binding to service response
The pdfSource variable below is the returned value from the service.
<embed [src]="sanitizer.bypassSecurityTrustResourceUrl(pdfSource)" type="application/pdf" width="100%" height="300px" />
See the Angular DomSanitizer docs for more info.
http
.post(url, data, {
responseType: "blob",
observe: "response"
})
.pipe(
map(response => {
saveAs(response.body, "fileName.pdf");
})
);
Extending what #ThierryTemplier did (the accepted answer) for Angular 8.
HTML:
<button mat-raised-button color="accent" (click)="downloadFile()">Download</button>
TypeScript:
downloadFile() {
this.http.get(
'http://localhost:4200/assets/other/' + this.fileName, {responseType: 'blob'})
.pipe(tap( // Log the result or error
data => console.log(this.fileName, data),
error => console.log(this.fileName, error)
)).subscribe(results => {
saveAs(results, this.fileName);
});
}
Sources:
FileSaver
Angular Http Client