Apollo-link: networkError at apollo-link-error n'a pas d'attribut de statut

Créé le 2 déc. 2017  ·  35Commentaires  ·  Source: apollographql/apollo-link

En tant que sujet. Merci

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

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

Commentaire le plus utile

Alors pour ceux qui cherchent encore...

La valeur statusCode est renvoyée en tant que propriété sur networkError, et le typage mis à jour pour networkError a été ajouté ici : https://github.com/apollographql/apollo-link/pull/530

Étant donné que networkError est maintenant un type d'union de Error | ServerError | ServerParseError , Typescript nous permettra uniquement d'accéder aux propriétés communes à tous les types de l'union. statusCode n'existe pas sur le type Error . Nous ne pourrons pas y accéder via networkError.statusCode sans se plaindre de Typescript ; nous devons utiliser un type guard ou autre chose pour différencier les types.

Plusieurs méthodes sont suggérées dans le manuel Typescript : https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types

Pour mes besoins, j'ai utilisé l'opérateur in pour faire fonctionner les choses.

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

@JoviDeCroock Vous pensez que ce problème peut être fermé en toute sécurité ?

Tous les 35 commentaires

Comme je le sais, "échec de la récupération" se produit s'il n'y a pas de réponse (par exemple, le serveur est en panne). Cela parce que vous ne verrez aucun code d'état. S'il vous plait corrigez moi si je me trompe.

Mais je pense que dans de telles situations, ce n'est pas la meilleure idée de n'avoir qu'un message texte - il devrait y avoir quelque chose qui peut être géré plus facilement (comme un code d'erreur).

J'ai vu que la demande était envoyée et reçue (401) dans la balise réseau de l'inspecteur Chrome.

Je pense que c'est lié à #218 (pas sûr..)

Ayant également ce problème. networkError.status ou networkError.statusCode sont manquants et l'objet de réponse est manquant. Il n'y a aucun moyen de gérer les codes d'état HTTP dans la couche réseau.

J'ai aussi ce problème actuellement. Il était censé être corrigé dans la dernière version, mais j'ai toujours ce problème.

des pensées à ce sujet @jbaxleyiii ?

Je suis désolé d'apprendre que vous, @akrigline et @bogdansoare ,

Je travaille sur #364 en ce moment. En attendant, pourriez-vous créer ou modifier ce code sandbos pour reproduire l'erreur ? Ou encore mieux ouvrir un PR avec un test raté ?

des nouvelles?

Ayant également ce problème, l'objet de réponse est vide et il n'y a pas de statusCode sur l'objet networkError.

Je n'ai même pas la propriété networkError :
screen shot 2018-05-07 at 13 01 56

EDIT : Très probablement #475

J'ai résolu ce problème en ignorant le type Error , mais c'est une solution complètement temporaire et nous devons attendre les futurs correctifs. @evans

Voici ce problème !

Le courant des networkError que nous avons écrit à leur sujet n'était pas une véritable erreur côté serveur. En résumé, nous n'aurons pas du tout statusCode dans ces cas et en fait, nous devons créer ou recevoir une véritable erreur réseau sur le serveur graphql. (_Si vous avez juste un problème de communication avec le serveur, vous ne pouvez pas accéder au statusCode _).

screen shot 2018-05-07 at 20 38 09

L'erreur de police est toujours là !

Si vous utilisez TypeScript, vous pouvez voir que networkError a le type suivant :

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

Et Error est défini comme :

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

Nous pouvons voir que le statusCode n'est pas défini ici, nous ne pouvons donc pas appeler quelque chose comme networkError.statusCode dans TypeScript.

Ma solution temporaire

Nous devons convertir networkError en any et en faire un object vide.

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}`);
});

D'après les codes afterWareLink ci-dessus, nous avons rencontré une erreur de serveur avec le code d'état 400 et nous pourrons désormais accéder à ce code d'état :

screen shot 2018-05-07 at 20 28 03

Voici ma définition 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,
});

En espérant être utile.

Si vous utilisez un serveur de nœuds et avez accès à votre objet de réponse à partir du contexte, vous pouvez résoudre ce problème en ajoutant ; ctx.res.status(401); avant de retourner votre réponse.
Désormais, apollo-link-errors et apollo-link-retry pourront tous les deux récupérer l'erreur de l'objet networkErrors.

Voici un exemple où j'utilise une directive de schéma personnalisée pour m'assurer qu'un utilisateur est authentifié ;

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;
        }
      };
    });
  }
}

C'était réparé ou pas ? Je ne peux toujours pas accéder à un code d'état dans l'objet networkError.

@artsiompeshko
Je peux y accéder de cette façon dans 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)
})

des nouvelles à ce sujet? Même problème. Pas de networkError.statusCode disponible dans mon code javascript.

@goldenbearkin avons-nous peut-être tort de l'utiliser correctement ?

Nous devrions autoriser le code d'état du réseau dans l'erreur, il n'y a actuellement aucun moyen d'accéder à l'état du réseau. Si le networkError est en fait un objet de type HttpErrorResponse qui a une propriété status qui est toujours 0 où le code d'état d'origine dans la console est 401 ou 403

@lvlohammadi
La même solution de contournement peut-elle également être effectuée pour les graphQLErrors ? Je peux voir le code d'état pour networkErrors en utilisant votre solution de contournement - testé avec un 504. Mais lorsque l'authentification échoue, je m'attends à un code d'état graphQLError de 401. Mais aucun moyen d'y accéder actuellement. J'ai essayé l'option 'graphQLErrors = {} as any', mais en vain.

C'est bizarre, mais j'ai en fait la propriété statusCode sur networkError . Cependant, il n'y a pas une telle propriété dans l'interface, donc TS génère une erreur ici.

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);
  }
});

Sur le serveur, j'utilise koa et koa-passport , et le morceau de code correspondant est ici :

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

Étant donné que ce numéro est vraiment obsolète, je le ferme, mais si cela vous inquiète toujours, n'hésitez pas à rouvrir et je vous répondrai dès que possible.

@JoviDeCroock confronté à ce problème en ce moment... alors, quelle est la solution ? Comment obtenir le statut d'erreur de networkError ?

D'accord, vous êtes donc sûr qu'il s'agit d'une erreur networkError (pas de graphqlError ?) et que statusCode/status n'est pas défini ?

Est-ce une erreur dactylographiée ou vraiment indéfinie ? Pouvez-vous le reproduire?

Btw downvoting mon commentaire n'aide pas à résoudre cette erreur, il n'y a jamais eu aucun moyen de reproduire ce problème et les problèmes devaient être supprimés pour améliorer la façon dont nous pouvons aider.

J'ai le même problème, mais cela peut être dû à la façon dont j'envoie le statut du serveur. J'essaie essentiellement de raccourcir l'une des manipulations spécifiques à graphQL et de répondre simplement avec, par exemple :
res.status(404).send("The resource you are looking for cannot be found")

Cependant, je ne reçois que TypeError: Failed To Fetch . Est-ce à cause des détails d'implémentation dans Apollo-Server-Express ?

@JoviDeCroock En fait, je me suis un peu trompé avec le code graphql en essayant d'implémenter la gestion des erreurs côté client pour 401 ou 403. Nous utilisons un middleware, et ce que j'ai fait, c'est simplement rendre toujours 401 pour chaque demande. De cette façon, la demande OPTION a également obtenu 401, ce qui ne devrait pas se produire, et probablement à cause de cela, je n'ai pas pu voir l'état du tout. Après avoir mis à jour le code, je peux voir les codes d'état

Merci pour la mise à jour @dimitriy-k heureux d'apprendre que ce problème est résolu.

Pour @AlbertoPL , je me pencherai sur votre problème ce soir. N'hésitez pas à donner plus de commentaires à ce sujet si vous le pouvez.

@JoviDeCroock Merci d'avoir jeté un coup d'œil. Le code spécifique que j'essaie de faire fonctionner est donc le suivant :
res.status(426).send()

Dans l'onglet réseau de Chrome, je vois que le point de terminaison /graphql répond avec un code d'état 426 et une réponse appropriée (mise à niveau requise). Cependant, je ne vois toujours que TypeError: Failed to Fetch lors de l'utilisation du lien onError de apollo-link-error . Si je pouvais obtenir le vrai code de statut, ce serait génial (ou même le message suffirait).

Très bien, c'est une très bonne information. Nous devrions être capables de déboguer cela d'une manière ou d'une autre, vous sentez-vous à la hauteur ou ?

Oui, je peux certainement essayer. J'apprécierais toute aide/conseils pour le faire.

Donc, en parcourant simplement le code dans le débogueur de Chrome, je peux voir que apollo-link-batch-http et apollo-link-http effectuent des appels d'API à l'aide de la fonction fetcher (soit transmise comme option de lien, soit par défaut, j'ai essayé les deux) :

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

Il n'entre jamais dans aucun des blocs then, mais plutôt dans le bloc catch. À ce moment-là, l'erreur est déjà définie sur "Échec de la récupération". Aucun code d'état n'est présent.

Nous devrions vérifier si cela ne va pas ici :

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

Je vais essayer de faire une reproduction ce week-end, ayant un emploi du temps assez chargé au bureau.

ÉDITER:

Le problème principal est que chaque fois que je teste cela, j'utilise apollo-server et cela se passe bien, mais il se peut que les liaisons ou quelque chose du genre soient un peu différentes. Il m'est donc difficile de tester cela sans serveur comme reproduction

Si cela peut vous aider, voici notre serveur Apollo (notez que nous utilisons Apollo-server-express). CreateGraphQLSchema renvoie l'instance d'Apollo Server que nous utilisons :

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 J'ai également rencontré ce problème. Il s'avère que c'est un problème CORS , pas un client Apollo.

Une promesse fetch() sera rejetée avec une TypeError lorsqu'une erreur réseau est rencontrée ou que CORS est mal configuré côté serveur

Assurez-vous de renvoyer les en-têtes appropriés lorsque vous rejetez la demande. Dans mon cas, j'utilisais API Gateway et mon autorisateur personnalisé rejetait la demande, mais n'attaquait pas l'en-tête Access-Control-Allow-Origin . J'utilise serverless pour gérer les déploiements, mais il peut être converti en modèles CloudFormation si c'est ce qui est utilisé : https://serverless.com/blog/cors-api-gateway-survival-guide/#cors -with-custom-authorizers.

Belle trouvaille @chris-feist !
L'ajout de l'en-tête Access-Control-Allow-Origin à la réponse a rendu le code d'état disponible pour apollo-client

Dans express.js, j'ai simplement fait ce qui suit :

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

L'utilisation de Access-Control-Allow-Origin ne présente-t-elle pas un risque de sécurité potentiel ? https://stackoverflow.com/questions/12001269/what-are-the-security-risks-of-setting-access-control-allow-origin

Alors pour ceux qui cherchent encore...

La valeur statusCode est renvoyée en tant que propriété sur networkError, et le typage mis à jour pour networkError a été ajouté ici : https://github.com/apollographql/apollo-link/pull/530

Étant donné que networkError est maintenant un type d'union de Error | ServerError | ServerParseError , Typescript nous permettra uniquement d'accéder aux propriétés communes à tous les types de l'union. statusCode n'existe pas sur le type Error . Nous ne pourrons pas y accéder via networkError.statusCode sans se plaindre de Typescript ; nous devons utiliser un type guard ou autre chose pour différencier les types.

Plusieurs méthodes sont suggérées dans le manuel Typescript : https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types

Pour mes besoins, j'ai utilisé l'opérateur in pour faire fonctionner les choses.

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

@JoviDeCroock Vous pensez que ce problème peut être fermé en toute sécurité ?

@mrkrli Merci. Solution décente.

Cette page vous a été utile?
0 / 5 - 0 notes