I want to serve spa app with nestjs and fastify, it works but when I refresh a 404 is triggered. So I wrote an exception to catch the 404 and send the file but I keep getting FST_ERR_REP_ALREADY_SENT error.
My code below
import { Catch, ExceptionFilter, ArgumentsHost, HttpException, NotFoundException } from '#nestjs/common';
import { FastifyReply } from 'fastify';
#Catch(NotFoundException)
export class NotFoundExceptionFilter implements ExceptionFilter {
catch(_exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse() as FastifyReply;
response.sendFile('index.html');
}
}
I want 404 to serve the index.html file
I have had this issue in the past and an update from Nest 8 to 9.x fixed the bug. #Cavdy you may want to update to the latest Nest, atleast anything >=9 and you should be good to go.
It's probably catching something on notFoundHandler before it reaches your exception filters. I recommend setting it on notFoundHandler instead of using exception filters reference
Related
I have created a few onCall cloud functions using Firebase. These functions interact with Stripe through the API. When I use AngularFire, or more specifically, AngularFireFunctions to call these said cloud functions, I receive the error message A bad HTTP response code (404) was received when fetching the script. in Chrome developer console. Yet, the expected result is received with no problem and the Firebase console displays a 200 response with no error messages or warnings. The project is entirely hosted on Firebase.
The 404 error also does not display a file that it is connected to in the console as such errors typically do within that console.
UPDATE
I also feel it is relevant to include, the Stripe developer logs in the dashboard reflect no errors, but a successfull call upon checking.
I have also tried to remove the call to Stripe in the cloud function and simply only return a string return 'The customer ID is:'+ ${data.customerId}+'. Thank you.' and still received the same error message.
I have also tried this solution, https://github.com/angular/angularfire/issues/1933#issuecomment-432910986 with the following code being placed inside app.module.ts however, am unable to find where FunctionsRegionToken would be defined to be able to import it.
providers: [
{ provide: FunctionsRegionToken, useValue: 'us-central1' }
]
Although, I'm not sure how changing the region to the same region the function is being called from currently would make any difference.
When you explore the Network tab of the developer console and visit the page that calls the function, you see that something is trying to call http://localhost:4200/firebase-messaging-sw.js which doesn't exist. The amount of calls to this file and the errors in the console coincide with each other which leads me to believe they are related.
END OF UPDATE
I have tried to add CORS to my cloud function (and am using it in onRequest functions), I've tried rewriting my cloud function, and even tried changing the client side function that calls the onCall to no avail. The only way to remove the error is to remove the call to the function, thus I've narrowed it down to something with the AngularFireFunctions.
What I am using and the versions
Angular V13
Firebase 9.6.7
Angular Fire 7.2.1
Node 16.13.1
What follows is my code, broken up into sections.
Cloud function
const cors = require('cors')({origin: true});
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
const FieldValue = require('firebase-admin').firestore.FieldValue;
admin.initializeApp();
const firebaseApp = admin.app();
const firebaseDB = firebaseApp.firestore();
const Stripe = require('stripe');
const stripe = Stripe(functions.config().stripe.key);
export const retrieveCustomer = functions.https.onCall( async(data) => {
if(data.customerId) {
const customer = await stripe.customers.retrieve(data.customerId);
if(customer) {
return customer;
} else {
throw new functions.https.HttpsError('unknown', 'An unknown error occurred, please try again.');
}
} else {
throw new functions.https.HttpsError('invalid-argument', 'A customer ID must be provided.');
}
});
Angular Service
import { Injectable } from '#angular/core';
import { AngularFirestore } from '#angular/fire/compat/firestore';
import { AngularFireFunctions } from '#angular/fire/compat/functions';
#Injectable({
providedIn: 'root'
})
export class BillingService {
constructor( private aff: AngularFireFunctions, private afs: AngularFirestore ) { }
RetrieveCustomer(customerId:string) {
const callable = this.aff.httpsCallable('retrieveCustomer');
return callable({
customerId: customerId
});
}
}
Angular Component
import { Component, OnInit, AfterContentInit } from '#angular/core';
import { BillingService } from 'src/app/shared/services/billing/billing.service';
#Component({
selector: 'app-billing-settings',
templateUrl: './billing-settings.component.html',
styleUrls: ['./billing-settings.component.css']
})
export class BillingSettingsComponent implements OnInit, AfterContentInit {
public stripeCustomer!: any;
constructor( private billingService: BillingService ) { }
ngOnInit(): void {
}
ngAfterContentInit(): void {
this.billingService.RetrieveCustomer('cus_LGRX8TPVF3Xh0w').subscribe((customer:any) => {
console.log(customer);
});
}
}
I'm trying a create a shared Guard as an external library in order to be imported and used across services. I'm not doing anything special that what is described in some guides but with the particularity that the code will reside in a shared library. Everything is working but the Exception to return a 401 error.
My guard looks something like this:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class MainGuard extends AuthGuard('jwt') {}
Nothing else. If I use that in a service folder it works, but at the time that I move as in their own library, the response changes.
The way that I'm using in the service has nothing special:
import { MainGuard } from 'shared-guard-library';
import { Controller, Get, UseGuards } from '#nestjs/common';
import { SomeService } from './some.service';
#Controller()
export class SomeController {
constructor(private someService: SomeService) {}
#Get('/foo')
#UseGuards(MainGuard)
async getSomething(): Promise<any> {
return this.someService.getSomething();
}
}
The client receives an error 500:
http :3010/foo
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 52
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Dec 2021 04:11:42 GMT
ETag: W/"34-rlKccw1E+/fV8niQk4oFitDfPro"
Keep-Alive: timeout=5
Vary: Origin
X-Powered-By: Express
{
"message": "Internal server error",
"statusCode": 500
}
And in the logs shows:
[Nest] 93664 - 12/08/2021, 10:11:42 PM ERROR [ExceptionsHandler] Unauthorized
UnauthorizedException: Unauthorized
at MainGuard.handleRequest (/sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:68:30)
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:49:128
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:86:24
at allFailed (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:101:18)
at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:174:28)
at Object.strategy.fail (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:296:9)
at Object.JwtStrategy.authenticate (/sharedGuardLibrary/node_modules/passport-jwt/lib/strategy.js:96:21)
at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:360:16)
at authenticate (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:361:7)
at /sharedGuardLibrary/node_modules/#nestjs/passport/dist/auth.guard.js:91:3
The logs are telling me that the correct exception was thrown, but is ignored at some point and I don't know the reason. Again: the same code in the same project works.
I took a look at the original class and I don't see any particular way to treat the exception
Any clue or guide it will appreciate.
So, this happens to be a "feature" of Typescript and how JavaScript object equality works in general. So in Nest's BaseExceptionFilter there's a check that exception instanceof HttpException, and normally, UnauthorizedException would be an instance of this, but because this is a library there's a few things that need to be considered.
All of the NestJS dependencies you're using have to be peerDependencies. This makes sure that when the library is installed, there's only one resulting package for the #nestjs/* package.
during local development, you'll need to take care to ensure that you're not resolving multiple instances of the same package (even if it's the exact same version, to JavaScript { hello: 'world' } === { hello: 'world' } // false). To take care of this, things like npm/yarn/pnpm link should not be used, but instead you should copy the dist and the package.json to the main application's node_modules/<package_name> directory.
a. The other option is using a monorepo tool like Nest's monorepo approach or Nx which have single package version approaches, and use the paths of the libraries rather than internal links.
If you follow this, when your production application installs the npm library, everything will work without an issue. It's an annoyance for sure, but it's a side effect of how JavaScript works
I had a similar problem with a custom auth package inside a monorepo.
My auth-library exposed AuthModule, JwtAuthGuard, and some utility functions. All needed packages were installed under my library so any other projects that were using it had not installed other versions of dependencies. Unfortunately using a custom guard caused Internal Server Error.
I've solved this issue by adding a global custom exception filter. It looks for a workaround but at least solves this issue.
This filter is exported from auth-library, so UnauthorizedException indicates on the same object as AuthGuard.
import { ArgumentsHost, Catch, ExceptionFilter, UnauthorizedException } from '#nestjs/common';
import { Response } from 'express'
#Catch(UnauthorizedException)
export class UnauthorizedExceptionFilter implements ExceptionFilter {
public catch(exception: UnauthorizedException, host: ArgumentsHost): Response {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.status(401).json({ statusCode: 401 });
}
}
See Extending Guards: https://docs.nestjs.com/security/authentication#extending-guards
In most cases, using a provided AuthGuard class is sufficient. However, there might be use-cases when you would like to simply extend the default error handling or authentication logic. For this, you can extend the built-in class and override methods within a sub-class.
Implement handleRequest(err, user, info) as follows:
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
I want to save each request (path, method, and userId) that comes to the server without having to hit the database twice, and also without messing up the main logic in services files with transactions.
Initially, I was trying to use an interceptor because it gets invoked after auth guards "which attaches the user to request" and before request handlers, but I faced two issues.
first, the fact that the interceptor will call the database to save a new record and then forward the request to handlers which will again hit DB again to handle the request. Secondly, It didn't work anyway because of dependancy injection problems.
code below is not working due to dependency errors as I mentioned, but It will give u an idea about what I need to acheive.
import { Injectable,
NestInterceptor,
Inject,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus } from '#nestjs/common';
import { Observable } from 'rxjs';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { HistoryEntity } from '../../modules/history/history.entity';
#Injectable()
export class HistoryInterceptor implements NestInterceptor {
constructor(
#Inject(getRepositoryToken(HistoryEntity))
private readonly historyRepo: Repository<HistoryEntity>
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { user, path, method } = request
if (!user) {
throw new HttpException('something terrible happened!', HttpStatus.BAD_GATEWAY);
}
const history = this.historyRepo.create({
path,
userId: user.id,
});
this.historyRepo.save(history);
return next.handle();
}
}
PS. from a performance point of view, It would also be great to not halt the request execution to save these info in db, in other words, Is it ok to NOT use await in this particular situation? because essecntially this is a system related operation and so, Node [rocess shouldn't wait for this step to process and return response to client.
Thanks in advance.
AWS X-Ray is support Express and Restify middleware but not support Nest.js.
Nest.js can't open segment and close segment to AWSXRay because it routes with typescript decoration.
How to use the AWS X-Ray with the Nest.js
Hmm, this is one of those situations that could be very interesting and difficult to work with. You can of course set up the openSegement call in the standard Nest middleware (looks just like Express middleware), but the closeSegment is a bit more difficult. I think (and I'm taking a long shot here as I have no real way to test this) you can create an interceptor and inject the HttpAdapter into it, check in incoming route before the request is made and see if it is a route you want to cover with X-Ray, if so mark a boolean and in the observable response (next.handle()) you can get the HttpAdapter instance and call the closeSegment function. In other words (and this will be really rough code):
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '#nestjs/common';
import { HttpAdapterHost } from '#nesjts/core';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import * as xRay from 'aws-xray-sdk-express';
#Injectable
export class XRayInterceptor implements NestInterceptor {
constructor(private readonly httpAdapter: HttpAdapterHost) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
let coverRoute = false;
const req = context.switchToHttp().getRequest();
if (req.path === 'yourPath') {
coverRoute = true;
}
return next.handle()
.pipe(
tap(() => (coverRoute && this.httpAdapter.use(xRay.xrayExpress.closeSegment()))
);
}
You might also probably be able to set up the openSegment in the interceptor as well, but again, all of this is untested and may not work as expected. I'm jsut trying to think of a way to maybe make this possible. Without access to error handling middleware your options would be to look at interceptors and filters, and it seems the closeSegement is to be an error handler like filters would be, so I'm not sure how you would catch errors otherwise. Maybe a filter is the best route, you may just have to play with ideas from here. Hopefully someone can give a bit more insight.
I've checked the docs and found that there are some ways to ignore a certain error but what I want to do is the opposite of this. I want to include an error that is ignored by default. For example, 404 errors when clicking on a corrupted route inside my angular application or when landing on a route that doesn't exist.
Note: I don't want to catch 404 Ajax requests. I want to catch the errors when navigating to a route - an Angular route- that doesn't exist.
By default, angular will throw an unhandled exception for non existing routes. So, make sure that Djaty.trackBug(exception) is added to your error handler. If you don't register the ErrorHandler to your AppModule, you can create it as the following:
export class DjatyErrorHandler implements ErrorHandler {
handleError(err: Error): void {
// Calling `trackBug` to notify Djaty about all uncaught exceptions.
Djaty.trackBug(err);
}
}
And add it to AppModule providers
#NgModule({
// ...
providers: [{provide: ErrorHandler, useClass: DjatyErrorHandler}],
// ...
})
export class AppModule { }
If you are not interested in tracking any uncaught exception - Which is not recommended - you can subscribe to router.events and call Djaty.trackBug when the event is a NavigationError
Ex:
Add this code to app.component.ts
this.router.events.subscribe(ev => {
if (ev instanceof NavigationError) {
Djaty.trackBug(ev.error);
}
});