Como assunto. Obrigado
const errorLink = onError(({ networkError }) => {
if (networkError.status === 401) {
logout();
}
})
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
:
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
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
_).
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.
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:
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:
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.
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 deError | 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 tipoError
. Não poderemos acessá-lo por meio denetworkError.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.@JoviDeCroock Acha que é seguro fechar este problema?