Apollo-link: networkError en apollo-link-error no tiene atributo de estado

Creado en 2 dic. 2017  ·  35Comentarios  ·  Fuente: apollographql/apollo-link

Como sujeto. Gracias

const errorLink = onError(({ networkError }) => {
  if (networkError.status === 401) {
    logout();
  }
})

screen shot 2017-12-03 at 4 49 20 am

Comentario más útil

Entonces, para cualquiera que todavía esté buscando ...

El valor de statusCode se devuelve como una propiedad en networkError, y la escritura actualizada para networkError se agregó aquí: https://github.com/apollographql/apollo-link/pull/530

Dado que networkError ahora es un tipo de unión de Error | ServerError | ServerParseError , Typescript solo nos permitirá acceder a propiedades que son comunes a todos los tipos en la unión. statusCode no existe en el tipo Error . No podremos acceder a él a través de networkError.statusCode sin que Typecript se queje; necesitamos usar un tipo de protección o algo más para diferenciar los tipos.

En el manual de TypeScript se sugieren varios métodos: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types

Para mis propósitos, utilicé el operador in para que todo funcionara.

  onError: ({ networkError }: ErrorResponse) => {
    if (
      networkError &&
      'statusCode' in networkError &&
      networkError.statusCode === 401
    ) {
      // perform logout stuff
    }
  },

@JoviDeCroock ¿ Crees que es seguro cerrar este problema?

Todos 35 comentarios

Como sé, "no se pudo recuperar" ocurre si no hay respuesta (por ejemplo, el servidor no funciona). Eso es porque no verá ningún código de estado. Por favor, corríjame si estoy equivocado.

Pero creo que en tales situaciones no es la mejor idea tener solo un mensaje de texto; debería haber algo que se pueda manejar más fácilmente (como un código de error).

Vi que la solicitud se estaba enviando y en (401) en la etiqueta de red del inspector de Chrome.

Creo que esto está relacionado con el n. ° 218 (no estoy seguro ...)

También tengo este problema. networkError.status o networkError.statusCode faltan y falta el objeto de respuesta. No hay forma de manejar los códigos de estado HTTP en la capa de red.

También tengo este problema actualmente. Supuestamente se solucionó en la última versión, pero sigo teniendo este problema.

¿ Alguna idea sobre esto

¡Lamento saber que tú @akrigline y @bogdansoare todavía tienen problemas con esto! Esto definitivamente debería arreglarse.

Estoy trabajando en el n. ° 364 en este momento. Mientras tanto, ¿podría crear o modificar este código sandbos para reproducir el error? ¿O mejor aún, abrir un PR con una prueba fallida?

¿hay noticias?

Además, al tener este problema, el objeto de respuesta está vacío y no hay statusCode en el objeto networkError.

Ni siquiera obtengo la propiedad networkError :
screen shot 2018-05-07 at 13 01 56

EDITAR: Lo más probable # 475

He solucionado este problema ignorando el tipo Error , pero esta es una solución completamente temporal y tenemos que esperar correcciones futuras. @evans

¡Aquí está este problema!

La corriente de networkError que escribimos sobre ellos no fue un error real del lado del servidor. En resumen, no tendremos statusCode en absoluto en estos casos y, de hecho, necesitamos crear o recibir un error de red real en el servidor graphql. (_Si solo tiene un problema de comunicación con el servidor, no puede acceder al statusCode _).

screen shot 2018-05-07 at 20 38 09

¡Aún persiste el error de tipografía!

Si está utilizando TypeScript, puede ver que networkError tiene el siguiente tipo:

export interface ErrorResponse {
  graphQLErrors?: GraphQLError[];
  networkError?: Error;
  response?: ExecutionResult;
  operation: Operation;
}

Y Error se define como:

interface Error {
    name: string;
    message: string;
    stack?: string;
}

Podemos ver que statusCode no está definido aquí, por lo que no podemos llamar a algo como networkError.statusCode en TypeScript.

Mi solucion temporal

Tenemos que lanzar networkError como any y también lo hacemos como un object vacío.

const afterWareLink = onError(({operation, response, graphQLErrors = {}, networkError = {} as any}) => {
    const status: number = networkError && networkError.statusCode ? networkError.statusCode : null;
    debugHelper.error('apolloError', {
        operation,
        response,
        graphQLErrors,
        networkError,
        status,
    });

    // Do your job
    // if (status && HTTPExceptions[status])
    //     redirectService.redirectWithReload(`/error/${status}`);
});

De acuerdo con los códigos afterWareLink , encontramos un error del servidor con el código de estado 400 y ahora podremos acceder a este código de estado:

screen shot 2018-05-07 at 20 28 03

Aquí está mi definición de ApolloClient:

import {ApolloLink, from} from 'apollo-link';
import {application} from '../../constants/application';
import {ApolloClient} from 'apollo-client';
import {InMemoryCache} from 'apollo-cache-inmemory';
import {HttpLink} from 'apollo-link-http';
import {tokenStorage} from '../middleware';
import {debugHelper} from '../helpers/debugHelper';
import {onError} from 'apollo-link-error';
import {apolloCache} from './apolloCache';
import fetch from 'unfetch';

const API = new HttpLink({
    uri: application.API.URL,
    fetch: fetch
});

const cache = new InMemoryCache({
    dataIdFromObject: (object: any) => apolloCache.getID(object)
});

const afterWareLink = onError(({operation, response, graphQLErrors = {}, networkError = {} as any}) => {
    const status: number = networkError && networkError.statusCode ? networkError.statusCode : null;
    debugHelper.error('apolloError', {
        operation,
        response,
        graphQLErrors,
        networkError,
        status,
    });

    // Do your job
    // if (status && HTTPExceptions[status])
    //     redirectService.redirectWithReload(`/error/${status}`);
});

const middleWareLink = new ApolloLink((operation, forward) => {
    const token = tokenStorage.get();
    operation.setContext(context => ({
        ...context,
        headers: {
            ...context.headers,
            token: token ? token.token : '',
        },
    }));

    return forward(operation);
});

export const apolloClient = new ApolloClient({
    link: from([
        middleWareLink,
        afterWareLink,
        API
    ]),
    cache: cache,
});

Espero ser de utilidad.

Si está utilizando un servidor de nodo y tiene acceso a su objeto de respuesta desde el contexto, puede resolver esto agregando; ctx.res.status(401); antes de devolver su respuesta.
Ahora apollo-link-errors y apollo-link-retry podrán detectar el error del objeto networkErrors.

Aquí hay un ejemplo en el que estoy usando una directiva de esquema personalizado para asegurarme de que un usuario esté autenticado;

import { SchemaDirectiveVisitor } from "graphql-tools";
import { defaultFieldResolver } from "graphql";
import { createError } from "apollo-errors";

const AuthError = createError("AuthError", {
  message: "Not authorized"
});

export class IsAuthenticatedDirective extends SchemaDirectiveVisitor {
  visitObject(type) {
    this.ensureFieldsWrapped(type);
  }
  visitFieldDefinition(field, details) {
    this.ensureFieldsWrapped(details.objectType);
  }

  ensureFieldsWrapped(objectType) {
    if (objectType._authFieldsWrapped) return;
    objectType._authFieldsWrapped = true;

    const fields = objectType.getFields();
    Object.keys(fields).forEach(fieldName => {
      const field = fields[fieldName];
      const { resolve = defaultFieldResolver } = field;
      field.resolve = async function(...args) {
        const ctx = args[2];
        const result = await resolve.apply(this, args);
        if (ctx.req.user) {
          return result;
        } else {
          ctx.res.status(401);
          return null;
        }
      };
    });
  }
}

¿Fue arreglado o no? Todavía no puedo acceder a ningún código de estado en el objeto networkError.

@artsiompeshko
Puedo acceder a él de esta manera en TypeScript:

const networkErrorLink = onError(({networkError, operation, forward}: ErrorResponse) => {
  if (networkError) {
    switch (networkError['statusCode']) {
      case 401:
      case 422:
        if (window.location.pathname !== '/login') {
          Logger.error('Unauthorized or stale Authorization Session. Reloading page...')
          window.history.go()
        }
        break
    }
  }
  return forward(operation)
})

¿Alguna noticia sobre esto? El mismo problema. No hay networkError.statusCode disponibles en mi código javascript.

@goldenbearkin tal vez nos equivocamos al usarlo correctamente?

Deberíamos permitir el código de estado de la red en el error, actualmente no hay forma de acceder al estado de la red. Si el networkError es en realidad un objeto de tipo HttpErrorResponse que tiene una propiedad status que siempre es 0 donde el código de estado original en la consola es 401 o 403

@lvlohammadi
¿Se puede hacer la misma solución para graphQLErrors también? Puedo ver el código de estado para networkErrors usando su solución alternativa - probé esto con un 504. Pero cuando falla la autenticación, espero un código de estado graphQLError de 401. Pero no hay forma de acceder a él actualmente. Probé la opción 'graphQLErrors = {} como cualquier', pero fue en vano.

Esto es extraño, pero en realidad tengo la propiedad statusCode en networkError . Sin embargo, no existe tal propiedad en la interfaz, por lo que TS genera un error aquí.

const errorLink = onError(({ networkError, graphQLErrors, response }: ErrorResponse) => {
  if (graphQLErrors) {
    graphQLErrors.map(({ message, locations, path }: GraphQLError) => {
      showError(message);
    });
  }

  // @ts-ignore
  console.log('statusCode', networkError.statusCode); // outputs 401

  if (networkError) {
    showError(networkError.message);
  }
});

En el servidor utilizo koa y koa-passport , y el código correspondiente aquí es:

app.use(mount(graphQlPath, passport.authenticate('jwt', { session: false })));

Dado que este problema está realmente desactualizado, lo cerraré, pero si aún está preocupado por esto, no dude en volver a abrir y me pondré en contacto con usted lo antes posible.

@JoviDeCroock enfrenta ese problema en este momento ... entonces, ¿cuál es la solución? ¿Cómo obtengo el estado de error de networkError?

Muy bien, ¿está seguro de que es un error de red (¿no hay error graphql?) Y el código de estado / estado no está definido.

¿Es un error mecanografiado o realmente no está definido? ¿Puedes reproducirlo?

Por cierto, votar en contra de mi comentario no ayuda a resolver este error, nunca hubo forma de que se reprodujera este problema, además de los problemas que debían eliminarse para mejorar la forma en que podemos ayudar.

Tengo el mismo problema, sin embargo, podría deberse a cómo envío el estado desde el servidor. Básicamente estoy tratando de atajar cualquiera de los manejos específicos de graphQL y simplemente responder con, por ejemplo:
res.status(404).send("The resource you are looking for cannot be found")

Sin embargo, solo obtengo TypeError: Failed To Fetch . ¿Esto se debe a los detalles de implementación en Apollo-Server-Express?

@JoviDeCroock En realidad, me

Gracias por la actualización @ dimitriy-k feliz de saber que este problema está resuelto.

Para @AlbertoPL , investigaré su problema esta noche. Si puede, no dude en dar más comentarios al respecto.

@JoviDeCroock Gracias por echar un vistazo. Entonces, el código específico con el que estoy tratando de hacer que funcione es el siguiente:
res.status(426).send()

En la pestaña de red de Chrome, veo que el punto final / graphql está respondiendo con un código de estado 426 y una respuesta adecuada (se requiere actualización). Sin embargo, todavía solo veo TypeError: Failed to Fetch cuando uso el enlace onError de apollo-link-error . Si pudiera obtener el verdadero código de estado, sería genial (o incluso el mensaje sería suficiente).

Muy bien, esa es una información bastante buena. Deberíamos poder depurar esto de alguna manera, ¿te sientes con ganas de esto o?

Sí, ciertamente puedo intentarlo. Agradecería cualquier ayuda / consejo para hacerlo.

Entonces, simplemente revisando el código en el depurador de Chrome, puedo ver que tanto apollo-link-batch-http como apollo-link-http hacen llamadas a la API usando la función de recuperación (ya sea pasada como una opción de enlace o por defecto, probé ambas):

fetcher(chosenURI, options)
    .then(function (response) {
        operations.forEach(function (operation) { return operation.setContext({ response: response }); });
        return response;
    }).then(parseAndCheckHttpResponse(operations))

Nunca entra en ninguno de los bloques de entonces, sino que entra en el bloque de captura. Para entonces, el error ya está configurado como "Error al recuperar". No hay ningún código de estado presente.

Deberíamos comprobar si sale mal aquí:

https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http-common/src/index.ts#L118

Intentaré hacer una reproducción este fin de semana, ya que tengo una agenda bastante ocupada en el trabajo.

EDITAR:

El principal problema es que cada vez que pruebo esto, estoy usando apollo-server y va bien, pero podría ser que los enlaces o algo así sean un poco diferentes. Así que es difícil para mí probar esto sin un servidor como reproducción.

Si le ayuda, aquí está nuestro servidor Apollo (tenga en cuenta que estamos usando Apollo-server-express). CreateGraphQLSchema devuelve la instancia de Apollo Server que usamos:

const { ApolloServer, gql } = require("apollo-server-express");

const collectFragments = container => {
  return container
    .$list()
    .map(component => container[component])
    .join("\n");
};

const mergeObjects = container => {
  const types = container.$list().map(k => container[k]);
  return _.merge(...types);
};

function CreateGraphQLSchema(
  schema,
  queries,
  mutations,
  subscriptions,
  resolvers,
  graphqlFormatError,
) {
  const graphQlSchema = gql`
    ${collectFragments(schema.enum)}

    ${collectFragments(schema.type)}

    type Query {
      ${schema.Query}
    }

    ${collectFragments(schema.input)}

    type Mutation {
      ${schema.Mutation}
    }

    type Subscription {
      ${schema.Subscription}
    }

    schema {
      query: Query
      mutation: Mutation
      subscription: Subscription
    }
  `;

  const resolverMap = {};
  resolverMap.Query = mergeObjects(queries);
  resolverMap.Mutation = mergeObjects(mutations);
  resolverMap.Subscription = mergeObjects(subscriptions);
  resolvers.$list().forEach(type => (resolverMap[type] = resolvers[type]));

  return new ApolloServer({
    typeDefs: graphQlSchema,
    resolvers: resolverMap,
    context: ({ req, res }) => {
      return { req, res, user: req.user };
    },
    formatError: graphqlFormatError,
    playground: process.env.NODE_ENV !== "production",
  });
}

@AlbertoPL También me encontré con este problema. Resulta que es un problema de CORS , no de un cliente de Apollo.

Una promesa fetch () se rechazará con un TypeError cuando se encuentre un error de red o CORS esté mal configurado en el lado del servidor

Asegúrese de devolver los encabezados adecuados al rechazar la solicitud. En mi caso, estaba usando API Gateway y mi autorizador personalizado rechazaba la solicitud, pero no adjuntaba el encabezado Access-Control-Allow-Origin . Utilizo serverless para administrar implementaciones, pero se puede convertir a plantillas de CloudFormation si eso es lo que se usa: https://serverless.com/blog/cors-api-gateway-survival-guide/#cors -with-custom-authorizers.

Buen hallazgo @ chris-feist!
Agregar el encabezado Access-Control-Allow-Origin a la respuesta hizo que el código de estado estuviera disponible para apollo-client

En express.js, simplemente hice lo siguiente:

res.set('Access-Control-Allow-Origin', '*').status(401).send()

¿El uso de Access-Control-Allow-Origin no permite un riesgo de seguridad potencial? https://stackoverflow.com/questions/12001269/what-are-the-security-risks-of-setting-access-control-allow-origin

Entonces, para cualquiera que todavía esté buscando ...

El valor de statusCode se devuelve como una propiedad en networkError, y la escritura actualizada para networkError se agregó aquí: https://github.com/apollographql/apollo-link/pull/530

Dado que networkError ahora es un tipo de unión de Error | ServerError | ServerParseError , Typescript solo nos permitirá acceder a propiedades que son comunes a todos los tipos en la unión. statusCode no existe en el tipo Error . No podremos acceder a él a través de networkError.statusCode sin que Typecript se queje; necesitamos usar un tipo de protección o algo más para diferenciar los tipos.

En el manual de TypeScript se sugieren varios métodos: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types

Para mis propósitos, utilicé el operador in para que todo funcionara.

  onError: ({ networkError }: ErrorResponse) => {
    if (
      networkError &&
      'statusCode' in networkError &&
      networkError.statusCode === 401
    ) {
      // perform logout stuff
    }
  },

@JoviDeCroock ¿ Crees que es seguro cerrar este problema?

@mrkrlli Gracias. Solución decente.

¿Fue útil esta página
0 / 5 - 0 calificaciones