import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { ApolloClient, ApolloLink, InMemoryCache, Observable } from '@apollo/client/core'; // import { Apollo, gql } from 'apollo-angular'; import { HttpLink } from 'apollo-angular/http'; import { onError } from '@apollo/client/link/error'; import { setContext } from '@apollo/client/link/context'; import { APOLLO_OPTIONS } from 'apollo-angular'; import { Utils, KeycloakService } from '@esp/esp-common'; import { environment } from '../environments/environment'; declare var CONFIG: any; if (!environment.production) { window['CONFIG'] = environment; } const uri = CONFIG.GRAPHQL_SERVER; // GraphQL server & api location function getAuthorizationToken() { return localStorage.getItem('espToken') ? localStorage.getItem('espToken') : ''; } function getUserToken() { return localStorage.getItem('espUserToken') ? localStorage.getItem('espUserToken') : ''; } function createApollo(httpLink: HttpLink) { try { const ErrorLink = getErrorLink(); const authLink = getAuthLink(); return new ApolloClient({ link: ApolloLink.from([ErrorLink, authLink, httpLink.create({ uri })]), cache: new InMemoryCache(), defaultOptions: { watchQuery: { fetchPolicy: 'network-only', errorPolicy: 'all' }, query: { fetchPolicy: 'network-only', errorPolicy: 'all' }, }, }); } catch (error) { Utils.showError('createApollo()', error); } } function getAuthLink() { return setContext((_, { headers }) => { // get the authentication token from local storage const token = getAuthorizationToken(); if (!headers) { headers = {}; } if (localStorage.espUserToken) { headers['espUserToken'] = getUserToken(); } // add authorization for keycloak authentication if (!environment.AZURE_AUTHENTICATION && token) { headers['authorization'] = `Bearer ${token}`; } return { headers: { ...headers } }; }); } function getErrorLink() { return onError(({ graphQLErrors, networkError, operation, forward }) => { if (graphQLErrors) { return handleGraphQLError(graphQLErrors, operation, forward); } if (networkError) { Utils.showError('getErrorLink()', networkError); if (networkError['status'] === 400 && !environment.AZURE_AUTHENTICATION) { return reAuthentication(operation, forward); } } }); } function handleGraphQLError(graphQLErrors, operation, forward) { try { if (!environment.AZURE_AUTHENTICATION) { for (const err of graphQLErrors) { Utils.showError('handleGraphQLError()', `${err?.message}, Location: ${err?.locations}, Path: ${err?.path}`); switch (err?.extensions.code) { case 'UNAUTHENTICATED': // error code is set to UNAUTHENTICATED, when AuthenticationError thrown in resolver return reAuthentication(operation, forward); } } } } catch (error) { Utils.showError('handleGraphQLError()', error); } } function reAuthentication(operation, forward) { // modify the operation context with a new token const keycloak = KeycloakService.getKeycloak(); return new Observable(observer => { if (keycloak.updateToken) { keycloak .updateToken() .then(() => { keycloak.saveToken(); const oldHeaders = operation.getContext().headers; const token = getAuthorizationToken(); operation.setContext({ headers: { ...oldHeaders, authorization: token ? `Bearer ${token}` : null, }, }); }) .then(() => { const subscriber = { next: observer.next.bind(observer), error: observer.error.bind(observer), complete: observer.complete.bind(observer), }; // Retry last failed request forward(operation).subscribe(subscriber); }) .catch(error => { Utils.showError('reAuthentication()', error); handleAuthenticationAttempt(); }); } else { console.log('keycloak: reauthentication failed::', keycloak); handleAuthenticationAttempt(); } }); } function handleAuthenticationAttempt() { if (localStorage.authAttempt > 2) { localStorage.clear(); window.location.pathname = '/home'; } else { localStorage.authAttempt ? localStorage.authAttempt++ : localStorage.authAttempt = 1; window.location.reload(); } } @NgModule({ exports: [HttpClientModule], providers: [ { provide: APOLLO_OPTIONS, useFactory: createApollo, deps: [HttpLink], }, ], }) export class GraphQLModule {}