件åãšããŠã ããããšã
const errorLink = onError(({ networkError }) => {
if (networkError.status === 401) {
logout();
}
})
ç§ãç¥ã£ãŠããããã«ãããã§ããã«å€±æããŸãããã¯ãå¿çããªãå ŽåïŒããšãã°ããµãŒããŒãããŠã³ããŠããå ŽåïŒã«çºçããŸãã ããã¯ãã¹ããŒã¿ã¹ã³ãŒãã衚瀺ãããªãããã§ãã ç§ãééã£ãŠããå Žåã¯èšæ£ããŠãã ããã
ãããããã®ãããªç¶æ³ã§ã¯ãããã¹ãã¡ãã»ãŒãžã ããçšæããã®ã¯æåã®æ¹æ³ã§ã¯ãªããšæããŸãããšã©ãŒã³ãŒããªã©ãããç°¡åã«åŠçã§ãããã®ãããã¯ãã§ãã
ã¯ããŒã ã€ã³ã¹ãã¯ã¿ãŒã®ãããã¯ãŒã¯ã¿ã°ã§ãªã¯ãšã¹ããéä¿¡ãããŠããã®ã確èªããŸããïŒ401ïŒã
ããã¯ïŒ218ã«é¢é£ããŠãããšæããŸãïŒããããŸãã..ïŒ
ãŸãããã®åé¡ããããŸãã networkError.status
ãŸãã¯networkError.statusCode
ãæ¬ èœããŠãããå¿çãªããžã§ã¯ããæ¬ èœããŠããŸãã ãããã¯ãŒã¯å±€ã§HTTPã¹ããŒã¿ã¹ã³ãŒããåŠçããæ¹æ³ã¯ãããŸããã
ç§ãçŸåšãã®åé¡ãæ±ããŠããŸãã ææ°ã®ãªãªãŒã¹ã§ä¿®æ£ããããšæãããŸããããŸã ãã®åé¡ãçºçããŠããŸãã
ãã®@jbaxleyiiiã«ã€ããŠäœãèãã¯
@akriglineãš@bogdansoareã§ãŸã åé¡ãçºçããŠãããšã®ããšã§ãã
çŸåšãïŒ364ã«åãçµãã§ããŸãã ãããŸã§ã®éããšã©ãŒãåçŸããããã«ãã®ã³ãŒããµã³ãããäœæãŸãã¯å€æŽã§ããŸããïŒ ãŸãã¯ã倱æãããã¹ãã§PRãéãæ¹ãããã§ããããã
é£çµ¡ãã£ãïŒ
ãŸãããã®åé¡ããããããå¿çãªããžã§ã¯ãã¯ç©ºã§ãããnetworkErrorãªããžã§ã¯ãã«statusCodeã¯ãããŸããã
networkError
ããããã£ãååŸããŸããïŒ
ç·šéïŒããããïŒ475
Error
åãç¡èŠããŠãã®åé¡ãä¿®æ£ããŸããããããã¯å®å
šã«äžæçãªè§£æ±ºçã§ãããå°æ¥ã®ä¿®æ£ãåŸ
ã€å¿
èŠããããŸãã @evans
ç§ãã¡ããããã«ã€ããŠæžããnetworkError
ã®çŸåšã¯ãå®éã®ãµãŒããŒåŽã®ãšã©ãŒã§ã¯ãããŸããã§ããã èŠçŽãããšããããã®å Žåã statusCode
ã¯ãŸã£ããçºçããŸãããå®éãgraphqlãµãŒããŒã§å®éã®ãããã¯ãŒã¯ãšã©ãŒãäœæãŸãã¯åä¿¡ããå¿
èŠããããŸãã ïŒ_ãµãŒããŒãšã®éä¿¡ã«åé¡ãããå Žåã¯ã statusCode
_ã«ã¢ã¯ã»ã¹ã§ããŸããïŒã
TypeScriptã䜿çšããŠããå Žåã networkError
ã¿ã€ãã¯æ¬¡ã®ãšããã§ãã
export interface ErrorResponse {
graphQLErrors?: GraphQLError[];
networkError?: Error;
response?: ExecutionResult;
operation: Operation;
}
ãŸãã Error
ã¯æ¬¡ã®ããã«å®çŸ©ãããŸãã
interface Error {
name: string;
message: string;
stack?: string;
}
statusCode
ãããã§å®çŸ©ãããŠããªãããšãããããŸãããããã£ãŠãTypeScriptã§networkError.statusCode
ãããªãã®ãåŒã³åºãããšã¯ã§ããŸããã
networkErrorãany
ãšããŠãã£ã¹ãããå¿
èŠããããŸãããŸãã空ã®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}`);
});
äžèšã®afterWareLink
ã³ãŒãã«ãããšã400ã¹ããŒã¿ã¹ã³ãŒãã§ãµãŒããŒãšã©ãŒãçºçãããã®ã¹ããŒã¿ã¹ã³ãŒãã«ã¢ã¯ã»ã¹ã§ããããã«ãªããŸãã
ãããç§ã®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,
});
ã圹ã«ç«ãŠã°å¹žãã§ãã
ããŒããµãŒããŒã䜿çšããŠããŠãã³ã³ããã¹ãããå¿çãªããžã§ã¯ãã«ã¢ã¯ã»ã¹ã§ããå Žåã¯ã次ãè¿œå ããããšã§ããã解決ã§ããŸãã å¿çãè¿ãåã«ctx.res.status(401);
ã
ããã§ãapollo-link-errorsãšapollo-link-retryã®äž¡æ¹ãnetworkErrorsãªããžã§ã¯ããããšã©ãŒãååŸã§ããããã«ãªããŸãã
ããã¯ãã«ã¹ã¿ã ã¹ããŒããã£ã¬ã¯ãã£ãã䜿çšããŠãŠãŒã¶ãŒãèªèšŒãããŠããããšã確èªããäŸã§ãã
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;
}
};
});
}
}
ä¿®æ£ãããŸãããïŒ ããã§ããnetworkErrorãªããžã§ã¯ãã®ã¹ããŒã¿ã¹ã³ãŒãã«ã¢ã¯ã»ã¹ã§ããŸããã
@artsiompeshko
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)
})
ããã«é¢ãããã¥ãŒã¹ã¯ãããŸããïŒ åãåé¡ã ç§ã®JavaScriptã³ãŒãã§å©çšã§ããnetworkError.statusCode
ãããŸããã
@goldenbearkinå€åãããæ£ãã䜿çšããã®ã¯ééã£ãŠããŸããïŒ
ãšã©ãŒã§ãããã¯ãŒã¯ã¹ããŒã¿ã¹ã³ãŒããèš±å¯ããå¿
èŠããããŸããçŸåšããããã¯ãŒã¯ã¹ããŒã¿ã¹ã«ã¢ã¯ã»ã¹ããæ¹æ³ã¯ãããŸããã networkError
ã¯å®éã«ã¯HttpErrorResponse
ã¿ã€ãã®ãªããžã§ã¯ãã§ããã status
ããããã£ãåžžã«0
ã§ãããã³ã³ãœãŒã«ã®å
ã®ã¹ããŒã¿ã¹ã³ãŒãã¯401ãŸãã¯403ã§ãã
@lvlohammadi
ããšãã°ãgraphQLErrorsã«å¯ŸããŠãåãåé¿çãå®è¡ã§ããŸããïŒ åé¿çã䜿çšããŠnetworkErrorsã®ã¹ããŒã¿ã¹ã³ãŒãã確èªã§ããŸããããã504ã§ãã¹ãããŸããããã ããèªèšŒã倱æãããšãgraphQLErrorã¹ããŒã¿ã¹ã³ãŒãã¯401ã«ãªããšäºæ³ãããŸãããã ããçŸåšã¯ã¢ã¯ã»ã¹ã§ããŸããã 'graphQLErrors = {} as any'ãªãã·ã§ã³ãè©ŠããŸãããã圹ã«ç«ã¡ãŸããã§ããã
ããã¯å¥åŠã§ãããç§ã¯å®éã«æã£ãŠããstatusCode
ã®ããããã£ãnetworkError
ã ãã ããã€ã³ã¿ãŒãã§ã€ã¹ã«ã¯ãã®ãããªããããã£ããªããããTSã¯ããã§ãšã©ãŒãåºåããŸãã
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);
}
});
ãµãŒããŒã§ã¯koa
ãškoa-passport
ããã察å¿ããã³ãŒãã¯æ¬¡ã®ãšããã§ãã
app.use(mount(graphQlPath, passport.authenticate('jwt', { session: false })));
ãã®åé¡ã¯æ¬åœã«æ代é ããªã®ã§ãç§ã¯ãããéããŠããŸãããããã§ãããã«ã€ããŠå¿é ããŠããå Žåã¯ããæ°è»œã«åéããŠãã ãããã§ããã ãæ©ããé£çµ¡ããããŸãã
@JoviDeCroockã¯çŸåšãã®åé¡ã«çŽé¢ããŠããŸã...ããã§ã解決çã¯äœã§ããïŒ networkErrorãããšã©ãŒã¹ããŒã¿ã¹ãååŸããã«ã¯ã©ãããã°ããã§ããïŒ
äºè§£ããŸãããããã¯networkErrorïŒgraphqlErrorã§ã¯ãããŸãããïŒïŒã§ãããstatusCode / statusãæªå®çŸ©ã§ããïŒ
ããã¯ã¿ã€ãã¹ã¯ãªãããšã©ãŒã§ããããããšãæ¬åœã«æªå®çŸ©ã§ããïŒ åçŸã§ããŸããïŒ
ãšããã§ãç§ã®ã³ã¡ã³ãã«å察祚ãæããŠãããã®ãšã©ãŒã®è§£æ±ºã«ã¯åœ¹ç«ã¡ãŸããããã®åé¡ãåçŸããæ¹æ³ã¯ãããŸããã§ãããããã«ãæ¯æŽæ¹æ³ãæ¹åããããã«åé¡ãä¿®æ£ããå¿ èŠããããŸããã
åãåé¡ãçºçããŸããããµãŒããŒããã¹ããŒã¿ã¹ãéä¿¡ããæ¹æ³ãåå ã§ããå¯èœæ§ããããŸãã ç§ã¯åºæ¬çã«ãgraphQLåºæã®åŠçã®ãããããã·ã§ãŒãã«ããããããšããŠããã次ã®ããã«å¿çããã ãã§ãã
res.status(404).send("The resource you are looking for cannot be found")
ãã ãã TypeError: Failed To Fetch
ããååŸã§ããŸããã ããã¯ãApollo-Server-Expressã®å®è£
ã®è©³çŽ°ãåå ã§ããïŒ
@JoviDeCroockå®éã401ãŸãã¯403ã®ã¯ã©ã€ã¢ã³ãåŽãšã©ãŒåŠçãå®è£ ããããšãããšãã«ãgraphqlã³ãŒããå°ãæ··ä¹±ãããŸãããããã«ãŠã§ã¢ã䜿çšããŠãããèŠæ±ããšã«åžžã«401ãè¿ãã ãã§ãã ããããã°ãOPTIONãªã¯ãšã¹ãã401ãååŸããŸããããããã¯çºçããªãã¯ãã§ãããããããã®ãããã¹ããŒã¿ã¹ããŸã£ãã確èªã§ããŸããã§ããã ã³ãŒããæŽæ°ããåŸãã¹ããŒã¿ã¹ã³ãŒããèŠãããšãã§ããŸã
æŽæ°@ dimitriy-kã«æè¬ããŸãããã®åé¡ã解決ãããããšãããããæããŸãã
@AlbertoPLã«ã€ããŠã¯ãä»å€ããªãã®åé¡ã調æ»ããŸãã å¯èœã§ããã°ãããã«ã€ããŠãã£ãšã³ã¡ã³ããããŠãã ããã
@JoviDeCroockã芧ããã ãããããšãããããŸãã ãããã£ãŠãç§ãæ©èœãããããšããŠããç¹å®ã®ã³ãŒãã¯æ¬¡ã®ãšããã§ãã
res.status(426).send()
Chromeã®[ãããã¯ãŒã¯]ã¿ãã§ã/ graphqlãšã³ããã€ã³ãã426ã¹ããŒã¿ã¹ã³ãŒããšé©åãªå¿çïŒã¢ããã°ã¬ãŒããå¿
èŠïŒã§å¿çããŠããããšãããããŸãã ãã ãã apollo-link-error
ããonErrorãªã³ã¯ã䜿çšãããšã TypeError: Failed to Fetch
ãã衚瀺ãããŸããã ç§ãæ¬åœã®ã¹ããŒã¿ã¹ã³ãŒããååŸã§ããã°ãããã¯çŽ æŽãããããšã§ãïŒãŸãã¯ã¡ãã»ãŒãžã§ãããååã§ãïŒã
ããããŸãããããã¯ããªãçŽ æŽãããæ å ±ã§ãã ã©ããããããããããããã°ã§ããã¯ãã§ãããããã«æºè¶³ããŠããŸããïŒ
ãããç§ã¯ç¢ºãã«è©Šãããšãã§ããŸãã ããããããã®å©ã/ãã³ããããã ããã°å¹žãã§ãã
ãããã£ãŠãChromeã®ãããã¬ãŒã§ã³ãŒããã¹ãããå®è¡ããã ãã§ã apollo-link-batch-http
ãšapollo-link-http
ã®äž¡æ¹ããã§ããã£ãŒé¢æ°ã䜿çšããŠAPIåŒã³åºããè¡ãããšãããããŸãïŒãªã³ã¯ãªãã·ã§ã³ãŸãã¯ããã©ã«ãã®ãããããšããŠæž¡ãããäž¡æ¹ãè©ŠããŸããïŒã
fetcher(chosenURI, options)
.then(function (response) {
operations.forEach(function (operation) { return operation.setContext({ response: response }); });
return response;
}).then(parseAndCheckHttpResponse(operations))
ããã¯thenãããã¯ã®ãããã«ãå ¥ãããšã¯ãªãã代ããã«catchãããã¯ã«å ¥ããŸãã ãã®æãŸã§ã«ããšã©ãŒã¯ãã§ã«ãFailedtoFetchãã«èšå®ãããŠããŸãã ã¹ããŒã¿ã¹ã³ãŒãã¯ãããŸããã
ããã§åé¡ãçºçãããã©ããã確èªããå¿ èŠããããŸãã
ããªãå¿ããã¹ã±ãžã¥ãŒã«ã®ATMã䜿ã£ãŠãä»é±æ«ã«è€è£œãäœæããããšæããŸãã
ç·šéïŒ
äž»ãªåé¡ã¯ãããããã¹ããããã³ã«apollo-server
ã䜿çšããŠããããšã§ããããã¯ããŸããããŸããããã€ã³ãã£ã³ã°ãªã©ãå°ãç°ãªãå¯èœæ§ããããŸãã ãããã£ãŠãè€è£œãšããŠãµãŒããŒãªãã§ããããã¹ãããã®ã¯é£ããã§ã
ããã圹ç«ã€å Žåã¯ããããApollo-serverã§ãïŒApollo-server-expressã䜿çšããŠããããšã«æ³šæããŠãã ããïŒã CreateGraphQLSchemaã¯ã䜿çšããApolloServerã®ã€ã³ã¹ã¿ã³ã¹ãè¿ããŸãã
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ç§ããã®åé¡ã«ééããŸããã Apolloã¯ã©ã€ã¢ã³ãã§ã¯ãªãã CORSã®åé¡ã§ããããšãå€æããŸããã
ãããã¯ãŒã¯ãšã©ãŒãçºçããå ŽåããŸãã¯ãµãŒããŒåŽã§CORSãæ£ããæ§æãããŠããªãå ŽåãfetchïŒïŒpromiseã¯TypeErrorã§æåŠãããŸã
ãªã¯ãšã¹ããæåŠãããšãã¯ãé©åãªããããŒãè¿ããŠããããšã確èªããŠãã ããã ç§ã®å ŽåãAPI Gatewayã䜿çšããŠããŠãã«ã¹ã¿ã æ¿èªè
ããªã¯ãšã¹ããæåŠããŠããŸãããã Access-Control-Allow-Origin
ããããŒãæ·»ä»ããŠããŸããhttps ïŒ//serverless.com/blog/cors-api-gateway-survival-guide/#cors-with-custom-authorizersã
@ chris-feistãèŠã€ããŠãã ããïŒ
å¿çã«Access-Control-Allow-Origin
ããããŒãè¿œå ãããšãã¹ããŒã¿ã¹ã³ãŒããapollo-clientã§å©çšã§ããããã«ãªããŸã
express.jsã§ãç§ã¯åã«æ¬¡ã®ããšãè¡ããŸããã
res.set('Access-Control-Allow-Origin', '*').status(401).send()
Access-Control-Allow-Originã䜿çšãããšãæœåšçãªã»ãã¥ãªãã£ãªã¹ã¯ãçºçããŸãããïŒ https://stackoverflow.com/questions/12001269/what-are-the-security-risks-of-setting-access-control-allow-origin
ã ãããŸã æ¢ããŠãã人ã®ããã«...
statusCodeå€ã¯networkErrorã®ããããã£ãšããŠè¿ãããnetworkErrorã®æŽæ°ãããå ¥åãããã«è¿œå ãããŸããïŒ https ïŒ
networkError
ã¯Error | ServerError | ServerParseError
å
±çšäœåã«ãªã£ãŠãããããTypescriptã§ã¯ãå
±çšäœã®ãã¹ãŠã®åã«å
±éããããããã£ã«ã®ã¿ã¢ã¯ã»ã¹ã§ããŸãã statusCode
ã¯ã¿ã€ãError
ã¯ååšããŸããã Typescriptãæå¥ãèšããã«ã networkError.statusCode
çµç±ã§ã¢ã¯ã»ã¹ããããšã¯ã§ããŸããã ã¿ã€ããåºå¥ããã«ã¯ãã¿ã€ãã¬ãŒããªã©ã䜿çšããå¿
èŠããããŸãã
Typescriptãã³ãããã¯ã§ã¯ããã€ãã®æ¹æ³ãææ¡ãããŠããŸãïŒ
ç§ã®ç®çã§ã¯ã in
æŒç®åã䜿çšããŠåäœãããŸããã
onError: ({ networkError }: ErrorResponse) => {
if (
networkError &&
'statusCode' in networkError &&
networkError.statusCode === 401
) {
// perform logout stuff
}
},
@JoviDeCroockãã®åé¡ã¯å®å šã«
@mrkrlliããããšãã ãŸãšããªãœãªã¥ãŒã·ã§ã³ã
æãåèã«ãªãã³ã¡ã³ã
ã ãããŸã æ¢ããŠãã人ã®ããã«...
statusCodeå€ã¯networkErrorã®ããããã£ãšããŠè¿ãããnetworkErrorã®æŽæ°ãããå ¥åãããã«è¿œå ãããŸããïŒ https ïŒ
networkError
ã¯Error | ServerError | ServerParseError
å ±çšäœåã«ãªã£ãŠãããããTypescriptã§ã¯ãå ±çšäœã®ãã¹ãŠã®åã«å ±éããããããã£ã«ã®ã¿ã¢ã¯ã»ã¹ã§ããŸããstatusCode
ã¯ã¿ã€ãError
ã¯ååšããŸããã Typescriptãæå¥ãèšããã«ãnetworkError.statusCode
çµç±ã§ã¢ã¯ã»ã¹ããããšã¯ã§ããŸããã ã¿ã€ããåºå¥ããã«ã¯ãã¿ã€ãã¬ãŒããªã©ã䜿çšããå¿ èŠããããŸããTypescriptãã³ãããã¯ã§ã¯ããã€ãã®æ¹æ³ãææ¡ãããŠããŸãïŒ
ç§ã®ç®çã§ã¯ã
in
æŒç®åã䜿çšããŠåäœãããŸããã@JoviDeCroockãã®åé¡ã¯å®å šã«