HTTP Interceptors

CoreModule registers three HTTP_INTERCEPTORS, in this order:

providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor,             multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor,     multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: HttpErrorHandlerInterceptor, multi: true },
]

Angular runs them in declaration order on the way out and in reverse on the way back, so HttpErrorHandlerInterceptor sees the response first.

AuthInterceptor

src/app/core/auth/_services/auth.interceptor.ts

Attaches the session token to every outgoing request via AuthService.getHeaders(token):

public getHeaders(token: string) {
    return {
        'X-Session-Token': token,
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        Pragma: 'no-cache',
        Expires: '0',
    };
}

Requests made before a token exists pass through unmodified. The cache headers are deliberate: financial figures must never be served from a browser cache.

intercept() subscribes to addToken() synchronously and assigns the resulting handler to a local variable:

intercept(request, next): Observable<HttpEvent<any>> {
    let handler;
    this.addToken(request).subscribe(req => (handler = next.handle(req)));
    return handler;
}

This works only because getAccessToken() is of(localStorage.getItem(…​)) — synchronous. Changing the token source to anything asynchronous would return undefined from intercept() and break every request.

UnauthorizedInterceptor

Same file. Watches for 401 responses and ends the session:

  1. If the request carried the ignoreUnauthorizedToken header (exported by @payability/pyb-ng2-api), the header is stripped and the 401 is allowed through without logging the user out.

  2. Otherwise, if the store reports the user as logged in, logout() is dispatched.

  3. The error is always re-thrown so callers still see it.

This is what lets login and token-probe requests fail with a 401 without kicking an existing session.

HttpErrorHandlerInterceptor

src/app/core/pyb/services/http-error-handler/http-error-handler.interceptor.ts

Converts HTTP failures into user-visible toasts, with several special cases.

Handled statuses

By default a toast is shown for:

Status Constant

400

HttpErrorsEnum.BAD_REQUEST

403

HttpErrorsEnum.FORBIDDEN

500

HttpErrorsEnum.INTERNAL_SERVER_ERROR

The message is the translated COMMON.ERRORS.SOMETHING_WENT_WRONG, shown for 10 s and memoized after the first lookup.

Special cases

Condition Behaviour

401 + error_code === 'PASSWORD_RESET_REQUIRED'

Decodes the email out of the request’s Authorization: Basic header and redirects to auth/reset-password?required=<urlencoded email>. No toast.

401 + error_code === 'ACCOUNT_LOCKED'

Shows a 30 s danger toast at the top of the screen instructing the supplier to email support@payability.com.

400 on /execute_password_change

Shows the server’s own error.message rather than the generic copy, so password-policy violations are actionable.

Bypassing the interceptor

Add the bypassHttpErrorHandlerInterceptor header to suppress the toast when the caller renders its own error UI. Helpers live in http-error-handler.constants.ts:

export const httpErrorBypassKey = 'bypassHttpErrorHandlerInterceptor';

// Suppress the toast for every handled status
export const bypassHttpErrorHeader = { [httpErrorBypassKey]: [] };

// Suppress it only for the listed statuses
export const bypassHttpErrorHeaderFactory = (errorCodes: number[] = []) => ({
    [httpErrorBypassKey]: serializeErrorCodes(errorCodes),
});
// Handle 400 inline, let everything else toast as usual
this.http.post(url, body, {
    headers: bypassHttpErrorHeaderFactory([HttpErrorsEnum.BAD_REQUEST]),
});

The header is stripped from the request before it leaves the browser. An empty code list means "bypass everything"; a populated list means "bypass only these statuses".

The error is re-thrown in every path, so bypassing changes only whether a toast appears — never whether the caller sees the failure.