GraphQL Mutation 在 Playground 中有效,但在客户端代码中无效
GraphQL Mutation works in Playground but not in Client Code
在这里看到类似的问题,但无法弄清楚我做错了什么。我有这个突变刷新 refreshToken
和访问 token
:
type Auth {
user: User!
token: String!
refreshToken: String!
}
type Mutation {
refreshTokens: Auth!
}
解析器:
Mutation: {
refreshTokens: async (root, args, { req }, info) => {
const result = await issueNewToken(req);
return result;
},
}
授权助手:
issueNewToken: async (req) => {
try {
const token = req.headers.rt;
if (token) {
const decoded = await jwt.verify(token, jwtRefreshTokenSecret);
let user = await User.findById(decoded.id);
if (!user) {
throw new AuthenticationError("No user found.");
}
let tokens = await authFunctions.issueToken(user);
return { ...tokens, user };
}
} catch (err) {
throw new AuthenticationError("Invalid Refresh Token.");
}
},
然后需要刷新令牌的函数如果过期:
const refreshTokens = async () => {
const {
data: {
refreshTokens: {
user: { _id: id, address: address },
token: token,
refreshToken: refreshToken,
},
},
} = await renewTokenApiClient.mutate({
mutation: REFRESH_TOKENS,
});
localStorage.setItem("refreshtoken", refreshToken);
localStorage.setItem("token", token);
};
和突变:
export const REFRESH_TOKENS = gql`
mutation refreshTokens {
refreshTokens {
user {
_id
address
}
token
refreshToken
}
}
`;
现在,这在操场上有效。当我在 http header 中传递刷新令牌时,我得到了更新的令牌和 refreshToken。但是refreshTokens
上面的函数returns
"Cannot return null for non-nullable field Mutation.refreshTokens."
我在这里错过了什么?
更新:
问题确实是操作不正确 headers。在 Whosebug 上的一些文档和其他问题之后,我形成了以下函数,但是,该操作仍然没有得到 headers,即使我似乎在刷新令牌后将它们设置在 header 中(哪个工作正常)。
import {
ApolloClient,
createHttpLink,
fromPromise,
from,
} from "@apollo/client";
import { onError } from "@apollo/client/link/error";
import { REFRESH_TOKENS } from "../mutations/User";
import { setContext } from "@apollo/client/link/context";
import cache from "../cache";
let isRefreshing = false;
let pendingRequests = [];
const setIsRefreshing = (value) => {
isRefreshing = value;
};
const addPendingRequest = (pendingRequest) => {
pendingRequests.push(pendingRequest);
};
const headerLink = setContext((_, { headers }) => {
const token = localStorage.getItem("refreshtoken");
return {
headers: {
...headers,
rt: token ? token : "",
},
};
});
const renewTokenApiClient = new ApolloClient({
link: from([headerLink, createHttpLink({ uri: "/graphql" })]),
cache,
credentials: "include",
});
const resolvePendingRequests = () => {
pendingRequests.map((callback) => {
console.log(callback);
callback();
});
pendingRequests = [];
};
const refreshTokens = async () => {
const {
data: {
refreshTokens: {
user: { _id: id, address: address },
token: token,
refreshToken: refreshToken,
},
},
} = await renewTokenApiClient.mutate({
mutation: REFRESH_TOKENS,
});
localStorage.setItem("refreshtoken", refreshToken);
localStorage.setItem("token", token);
};
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
switch (err?.message) {
case "jwt expired":
const oldHeaders = operation.getContext().headers;
if (!isRefreshing) {
setIsRefreshing(true);
const headers = {
...operation.getContext().headers,
};
return fromPromise(
refreshTokens()
.then((refreshTokensResult) => {
let headers = {
//readd old headers
oldHeaders,
//switch out old access token for new one
authorization: `Bearer ${refreshTokensResult.token}`,
rt: refreshTokensResult.refreshToken,
};
operation.setContext({
headers,
});
console.log(operation, "ops");
})
.catch(() => {
resolvePendingRequests();
setIsRefreshing(false);
localStorage.clear();
return forward(operation);
})
).flatMap(() => {
resolvePendingRequests();
setIsRefreshing(false);
console.log(operation);
return forward(operation);
});
} else {
return fromPromise(
new Promise((resolve) => {
addPendingRequest(() => resolve());
})
).flatMap(() => {
return forward(operation);
});
}
}
}
} else if (networkError) console.log(`[Network error]: ${networkError}`);
}
);
export default errorLink;
UPD2: 找到解决方案并发布答案。
查看 issueNewToken
的代码,如果 header 中不存在令牌,则有一条路径将 return undefined
。目前尚不清楚您是如何创建 renewTokenApiClient
的,但是需要在某处指定 header 以随请求一起发送,它不像自动发送的 cookie。
我建议检查网络选项卡以查看请求中是否存在 header。如果不是,则根据客户端的使用情况,可以通过多种方式注入它。对于 apollo 客户端,你需要创建 a link 来注入它。
import { setContext } from '@apollo/client/link/context';
const authenticationLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
完成了这个,效果很好。 refreshTokens()
在 localStorage 中设置新令牌。
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
switch (err?.message) {
case "jwt expired":
// Let's refresh token through async request
return new Observable((observer) => {
refreshTokens()
.then(() => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
},
}));
})
.then(() => {
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
};
// Retry last failed request
forward(operation).subscribe(subscriber);
})
.catch((error) => {
// No refresh or client token available, we force user to login
localStorage.clear();
isLoggedInVar(false);
observer.error(error);
});
});
}
}
} else if (networkError) console.log(`[Network error]: ${networkError}`);
}
);
在这里看到类似的问题,但无法弄清楚我做错了什么。我有这个突变刷新 refreshToken
和访问 token
:
type Auth {
user: User!
token: String!
refreshToken: String!
}
type Mutation {
refreshTokens: Auth!
}
解析器:
Mutation: {
refreshTokens: async (root, args, { req }, info) => {
const result = await issueNewToken(req);
return result;
},
}
授权助手:
issueNewToken: async (req) => {
try {
const token = req.headers.rt;
if (token) {
const decoded = await jwt.verify(token, jwtRefreshTokenSecret);
let user = await User.findById(decoded.id);
if (!user) {
throw new AuthenticationError("No user found.");
}
let tokens = await authFunctions.issueToken(user);
return { ...tokens, user };
}
} catch (err) {
throw new AuthenticationError("Invalid Refresh Token.");
}
},
然后需要刷新令牌的函数如果过期:
const refreshTokens = async () => {
const {
data: {
refreshTokens: {
user: { _id: id, address: address },
token: token,
refreshToken: refreshToken,
},
},
} = await renewTokenApiClient.mutate({
mutation: REFRESH_TOKENS,
});
localStorage.setItem("refreshtoken", refreshToken);
localStorage.setItem("token", token);
};
和突变:
export const REFRESH_TOKENS = gql`
mutation refreshTokens {
refreshTokens {
user {
_id
address
}
token
refreshToken
}
}
`;
现在,这在操场上有效。当我在 http header 中传递刷新令牌时,我得到了更新的令牌和 refreshToken。但是refreshTokens
上面的函数returns
"Cannot return null for non-nullable field Mutation.refreshTokens."
我在这里错过了什么?
更新:
问题确实是操作不正确 headers。在 Whosebug 上的一些文档和其他问题之后,我形成了以下函数,但是,该操作仍然没有得到 headers,即使我似乎在刷新令牌后将它们设置在 header 中(哪个工作正常)。
import {
ApolloClient,
createHttpLink,
fromPromise,
from,
} from "@apollo/client";
import { onError } from "@apollo/client/link/error";
import { REFRESH_TOKENS } from "../mutations/User";
import { setContext } from "@apollo/client/link/context";
import cache from "../cache";
let isRefreshing = false;
let pendingRequests = [];
const setIsRefreshing = (value) => {
isRefreshing = value;
};
const addPendingRequest = (pendingRequest) => {
pendingRequests.push(pendingRequest);
};
const headerLink = setContext((_, { headers }) => {
const token = localStorage.getItem("refreshtoken");
return {
headers: {
...headers,
rt: token ? token : "",
},
};
});
const renewTokenApiClient = new ApolloClient({
link: from([headerLink, createHttpLink({ uri: "/graphql" })]),
cache,
credentials: "include",
});
const resolvePendingRequests = () => {
pendingRequests.map((callback) => {
console.log(callback);
callback();
});
pendingRequests = [];
};
const refreshTokens = async () => {
const {
data: {
refreshTokens: {
user: { _id: id, address: address },
token: token,
refreshToken: refreshToken,
},
},
} = await renewTokenApiClient.mutate({
mutation: REFRESH_TOKENS,
});
localStorage.setItem("refreshtoken", refreshToken);
localStorage.setItem("token", token);
};
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
switch (err?.message) {
case "jwt expired":
const oldHeaders = operation.getContext().headers;
if (!isRefreshing) {
setIsRefreshing(true);
const headers = {
...operation.getContext().headers,
};
return fromPromise(
refreshTokens()
.then((refreshTokensResult) => {
let headers = {
//readd old headers
oldHeaders,
//switch out old access token for new one
authorization: `Bearer ${refreshTokensResult.token}`,
rt: refreshTokensResult.refreshToken,
};
operation.setContext({
headers,
});
console.log(operation, "ops");
})
.catch(() => {
resolvePendingRequests();
setIsRefreshing(false);
localStorage.clear();
return forward(operation);
})
).flatMap(() => {
resolvePendingRequests();
setIsRefreshing(false);
console.log(operation);
return forward(operation);
});
} else {
return fromPromise(
new Promise((resolve) => {
addPendingRequest(() => resolve());
})
).flatMap(() => {
return forward(operation);
});
}
}
}
} else if (networkError) console.log(`[Network error]: ${networkError}`);
}
);
export default errorLink;
UPD2: 找到解决方案并发布答案。
查看 issueNewToken
的代码,如果 header 中不存在令牌,则有一条路径将 return undefined
。目前尚不清楚您是如何创建 renewTokenApiClient
的,但是需要在某处指定 header 以随请求一起发送,它不像自动发送的 cookie。
我建议检查网络选项卡以查看请求中是否存在 header。如果不是,则根据客户端的使用情况,可以通过多种方式注入它。对于 apollo 客户端,你需要创建 a link 来注入它。
import { setContext } from '@apollo/client/link/context';
const authenticationLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
完成了这个,效果很好。 refreshTokens()
在 localStorage 中设置新令牌。
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
switch (err?.message) {
case "jwt expired":
// Let's refresh token through async request
return new Observable((observer) => {
refreshTokens()
.then(() => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
},
}));
})
.then(() => {
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
};
// Retry last failed request
forward(operation).subscribe(subscriber);
})
.catch((error) => {
// No refresh or client token available, we force user to login
localStorage.clear();
isLoggedInVar(false);
observer.error(error);
});
});
}
}
} else if (networkError) console.log(`[Network error]: ${networkError}`);
}
);