Apollo-link: networkError em apollo-link-error não tem atributo de status

Criado em 2 dez. 2017  ·  35Comentários  ·  Fonte: apollographql/apollo-link

Como assunto. Obrigado

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

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

Comentários muito úteis

Então, para quem ainda está procurando ...

O valor statusCode é retornado como uma propriedade em networkError, e a digitação atualizada para networkError foi adicionada aqui: https://github.com/apollographql/apollo-link/pull/530

Como networkError agora é um tipo de união de Error | ServerError | ServerParseError , o Typescript só nos permitirá acessar propriedades que são comuns a todos os tipos na união. statusCode não existe no tipo Error . Não poderemos acessá-lo por meio de networkError.statusCode sem reclamar do Typescript; precisamos usar um protetor de tipo ou outra coisa para diferenciar os tipos.

Vários métodos são sugeridos no manual Typescript: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types

Para meus objetivos, usei o operador in para fazer as coisas funcionarem.

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

@JoviDeCroock Acha que é seguro fechar este problema?

Todos 35 comentários

Como eu sei, "falha ao buscar" acontece se não houver resposta (por exemplo, o servidor está fora do ar). Isso porque você não verá nenhum código de status. Por favor corrija-me se eu estiver errado.

Mas acho que em tais situações não é a melhor ideia ter apenas uma mensagem de texto - deve haver algo que possa ser tratado mais facilmente (como um código de erro).

Eu vi a solicitação estava enviando e em (401) na tag de rede do inspetor de cromo.

Acho que isso está relacionado ao # 218 (não tenho certeza ..)

Também tendo esse problema. networkError.status ou networkError.statusCode estão faltando e o objeto de resposta está faltando. Não há como lidar com códigos de status HTTP na camada de rede.

Eu também tenho esse problema atualmente. Supostamente, foi corrigido na versão mais recente, mas ainda estou tendo esse problema.

alguma opinião sobre isso @jbaxleyiii ?

Lamento saber que @akrigline e @bogdansoare ainda estão tendo problemas com isso! Isso definitivamente deve ser corrigido.

Estou trabalhando no # 364 agora. Enquanto isso, você poderia criar ou modificar esse sandbos de código para reproduzir o erro? Ou ainda melhor abrir um PR com um teste reprovado?

qualquer notícia?

Também tendo esse problema, o objeto de resposta está vazio e não há statusCode no objeto networkError.

Eu nem mesmo entendo a propriedade networkError :
screen shot 2018-05-07 at 13 01 56

EDITAR: Provavelmente # 475

Corrigi esse problema ignorando o tipo Error , mas essa é uma solução completamente temporária e temos que esperar por correções futuras. @evans

Aqui está esse problema!

O atual networkError que escrevemos sobre eles não foi um erro real do lado do servidor. Em resumo, não teremos statusCode em todos esses casos e, de fato, precisamos criar ou receber um erro de rede real no servidor graphql. (_Se você tem apenas um problema de comunicação com o servidor, não consegue acessar o statusCode _).

screen shot 2018-05-07 at 20 38 09

Ainda resta o erro de tipo de letra!

Se você estiver usando o TypeScript, verá que networkError tem o seguinte tipo:

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

E Error é definido como:

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

Podemos ver que statusCode não está definido aqui, então não podemos chamar algo como networkError.statusCode no TypeScript.

Minha solução temporária

Temos que lançar networkError como any e também torná-lo um object vazio.

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 acordo com os códigos afterWareLink , encontramos um erro de servidor com o código de status 400 e agora poderemos acessar este código de status:

screen shot 2018-05-07 at 20 28 03

Aqui está minha definição 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 útil.

Se você estiver usando um servidor de nó e tiver acesso ao seu objeto de resposta a partir do contexto, pode resolver isso adicionando; ctx.res.status(401); antes de retornar sua resposta.
Agora, apollo-link-errors e apollo-link-retry serão capazes de detectar o erro do objeto networkErrors.

Aqui está um exemplo em que estou usando uma diretiva de esquema personalizada para garantir que um usuário seja 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;
        }
      };
    });
  }
}

Foi consertado ou não? Ainda não consigo acessar nenhum código de status no objeto networkError.

@artsiompeshko
Posso acessá-lo desta forma no 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)
})

Alguma novidade sobre isso? Mesmo problema. Nenhum networkError.statusCode disponível em meu código javascript.

@goldenbearkin talvez estejamos errados ao usá-lo corretamente?

Devemos permitir o código de status da rede no erro, atualmente não há maneira de acessar o status da rede. O networkError é na verdade um objeto do tipo HttpErrorResponse que tem uma propriedade status que é sempre 0 onde o código de status original no console é 401 ou 403

@lvlohammadi
A mesma solução alternativa pode ser feita para graphQLErrors também? Posso ver o código de status para networkErrors usando sua solução alternativa - testei isso com um 504. Mas quando a autenticação falha, espero um código de status graphQLError de 401. Mas não há como acessá-lo no momento. Tentei a opção 'graphQLErrors = {} as any', mas sem sucesso.

Isso é estranho, mas na verdade eu tenho statusCode propriedade em networkError . No entanto, essa propriedade não existe na interface, portanto, o TS gera um erro aqui.

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

No servidor, eu uso koa e koa-passport , e o trecho de código correspondente aqui é:

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

Uma vez que este problema está realmente desatualizado, estou encerrando-o, mas se você ainda estiver preocupado com isso, reabra e entrarei em contato com você o mais rápido possível.

@JoviDeCroock está enfrentando esse problema agora ... então, qual é a solução? Como obtenho o status de erro do networkError?

Tudo bem, então você tem certeza que é um networkError (sem graphqlError?) E statusCode / status é indefinido?

É um erro de digitação ou realmente indefinido? Você pode reproduzi-lo?

Btw downvoting meu comentário não ajuda a resolver esse erro, nunca houve nenhuma maneira para este problema ser reproduzido e os problemas precisaram ser cortados para melhorar a forma como podemos ajudar.

Tenho o mesmo problema, mas pode ser por causa de como estou enviando o status do servidor. Basicamente, estou tentando criar um atalho para qualquer manipulação específica do GraphQL e simplesmente responder com, por exemplo:
res.status(404).send("The resource you are looking for cannot be found")

No entanto, recebo apenas TypeError: Failed To Fetch . É por causa dos detalhes de implementação no Apollo-Server-Express?

@JoviDeCroock Na verdade, eu baguncei um pouco o código do graphql ao tentar implementar o tratamento de erros do lado do cliente para 401 ou 403. Usamos middleware, e o que fiz foi apenas devolver sempre 401 para cada solicitação. Dessa forma também a solicitação OPTION obteve 401, o que não deveria acontecer, e provavelmente por causa disso eu não consegui ver o status de forma alguma. Depois de atualizar o código, posso ver os códigos de status

Obrigado pela atualização @ dimitriy-k feliz em saber que esse problema foi resolvido.

Para @AlbertoPL , analisarei seu problema esta noite. Sinta-se à vontade para fazer mais comentários sobre isso, se puder.

@JoviDeCroock Obrigado por dar uma olhada. Portanto, o código específico que estou tentando fazer funcionar é o seguinte:
res.status(426).send()

Na guia de rede do Chrome, vejo que o endpoint / graphql está respondendo com um código de status 426 e uma resposta apropriada (atualização necessária). No entanto, ainda vejo apenas TypeError: Failed to Fetch ao usar o link onError de apollo-link-error . Se eu pudesse obter o código de status verdadeiro, seria ótimo (ou mesmo a mensagem seria suficiente).

Tudo bem, essa é uma informação muito boa. Devemos ser capazes de depurar isso de alguma forma, você se sente bem para isso ou?

Sim, certamente posso tentar. Agradeço qualquer ajuda / dicas para fazer isso.

Então, apenas percorrendo o código no depurador do Chrome, posso ver que apollo-link-batch-http e apollo-link-http fazem chamadas de API usando a função de busca (passada como uma opção de link ou algum padrão, tentei ambos):

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

Ele nunca vai para nenhum dos blocos then, mas, em vez disso, vai para o bloco catch. Nesse momento, o erro já está definido como "Falha ao buscar". Nenhum código de status está presente.

Devemos verificar se isso dá errado aqui:

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

Vou tentar fazer uma reprodução neste fim de semana, tendo um caixa eletrônico bem lotado no trabalho.

EDITAR:

O principal problema é que toda vez que eu testo isso, estou usando apollo-server e funciona bem, mas pode ser que as ligações ou algo assim sejam um pouco diferentes. Portanto, é difícil para mim testar isso sem um servidor como reprodução

Se ajudar, aqui está nosso Apollo-server (observe que estamos usando Apollo-server-express). CreateGraphQLSchema retorna a instância do 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 Também tive esse problema. Acontece que é um problema do CORS , não do cliente Apollo.

Uma promessa fetch () será rejeitada com um TypeError quando um erro de rede for encontrado ou o CORS estiver configurado incorretamente no lado do servidor

Certifique-se de retornar os cabeçalhos adequados ao rejeitar a solicitação. No meu caso, eu estava usando o API Gateway e meu autorizador personalizado estava rejeitando a solicitação, mas não anexando o cabeçalho Access-Control-Allow-Origin . Eu uso o serverless para gerenciar implantações, mas ele pode ser convertido em modelos CloudFormation se for o que é usado: https://serverless.com/blog/cors-api-gateway-survival-guide/#cors -with-custom-authorizers.

Nice find @ chris-feist!
Adicionar o cabeçalho Access-Control-Allow-Origin à resposta tornou o código de status disponível para o cliente apollo

Em express.js, eu simplesmente fiz o seguinte:

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

O uso de Access-Control-Allow-Origin não permite um risco potencial à segurança? https://stackoverflow.com/questions/12001269/what-are-the-security-risks-of-setting-access-control-allow-origin

Então, para quem ainda está procurando ...

O valor statusCode é retornado como uma propriedade em networkError, e a digitação atualizada para networkError foi adicionada aqui: https://github.com/apollographql/apollo-link/pull/530

Como networkError agora é um tipo de união de Error | ServerError | ServerParseError , o Typescript só nos permitirá acessar propriedades que são comuns a todos os tipos na união. statusCode não existe no tipo Error . Não poderemos acessá-lo por meio de networkError.statusCode sem reclamar do Typescript; precisamos usar um protetor de tipo ou outra coisa para diferenciar os tipos.

Vários métodos são sugeridos no manual Typescript: https://www.typescriptlang.org/docs/handbook/advanced-types.html#type -guards-and-differentiating-types

Para meus objetivos, usei o operador in para fazer as coisas funcionarem.

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

@JoviDeCroock Acha que é seguro fechar este problema?

@mrkrlli Obrigado. Solução decente.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

j3ddesign picture j3ddesign  ·  3Comentários

ignivalancy picture ignivalancy  ·  5Comentários

lobosan picture lobosan  ·  3Comentários

MoonTahoe picture MoonTahoe  ·  3Comentários

zsolt-dev picture zsolt-dev  ·  4Comentários