刷新访问令牌并调用 foward(operation) 后查询不是 re-executed
Query not re-executed after refreshing access token and calling foward(operation)
由于我是 Apollo Client 的新手,我觉得我的知识可能存在根本性的差距。但是我已经仔细阅读了 Stack Overflow、GitHub 问题和 Google 以找到我 运行 遇到但尚未找到任何问题的明显解决方案。
基本上我有以下 Apollo 客户端设置(简化):
const auth = new Auth()
const authMiddleware = new ApolloLink((operation, forward) => {
const authToken = auth.getToken().access_token
console.log(authToken)
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
authorization: authToken ? `Bearer ${authToken}` : ''
}
}))
return forward(operation)
})
const cache = new InMemoryCache()
const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ extensions, locations, message, path }) => {
if (extensions.code === 'access-denied') {
auth.refresh()
.then(() => {
console.log(`new access token: ${auth.getToken().access_token}`)
return forward(operation)
}).catch((error) => {
handleLogout(error)
})
}
})
}
})
const handleLogout = (reason) => {
auth.logout()
}
const httpLink = new HttpLink({ uri: '' })
const client = new ApolloClient({
cache: cache,
link: ApolloLink.from([
errorLink,
authMiddleware,
httpLink
])
})
我有一个简单的查询:
client.query({
query: Queries.MyQuery
}).then((response) => {
console.log(response)
}, (error) => {
console.log(error)
})
如果第一次运行时有有效的 OAuth 访问令牌,客户端将成功执行查询。但是,如果我使 OAuth 服务器上的访问令牌过期,然后尝试执行查询,它不会成功完成。
调试时,我可以看到发生了什么:
authMiddleware
将旧访问令牌正确添加到请求 header。
- 请求失败,因为令牌不再有效。这由
errorLink
. 处理 属性
errorLink
也成功检索到新的访问令牌和 returns forward(operation)
.
authMiddleware
再次被调用,添加新的访问令牌,并且 returns forward(operation)
.
这就是问题所在。查询从不 re-executes。如果我手动刷新页面以 re-execute 查询,它将使用新的访问令牌并成功完成。
通过阅读文档,听起来我的设置方式应该可行,但显然我做错了什么。
我能够通过挖掘各种来源拼凑出正在发生的事情。之所以令人困惑,主要是因为许多开发人员过去一直在努力解决这个问题(现在似乎仍然如此),所以那里有大量过时的解决方案和帖子。
This GitHub issue was the most useful source of information, even though it's attached to a repository that's now deprecated. 也很有帮助。
我花了一些时间沿着使用实用方法将 promise 变成 Observable 的道路前进,但如果您使用 fromPromise
.
,则不再需要这样做
这是我最终得到的适用于 Apollo Client 3.2.0 的解决方案:
const authLink = new ApolloLink((operation, forward) => {
const authToken = auth.getToken().access_token
console.info(`access token: ${authToken}`)
operation.setContext(({ headers }) => ({
headers: {
...headers,
authorization: authToken ? `Bearer ${authToken}` : ''
}
}))
return forward(operation)
})
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
const firstGraphQLError = graphQLErrors[0]
if (firstGraphQLError.extensions.code === 'access-denied') {
let innerForward
if (!isRefreshing) {
isRefreshing = true
innerForward = fromPromise(
auth.refresh()
.then(() => {
const authToken = auth.getToken().access_token
console.info(`access token refreshed: ${authToken}`)
resolvePendingRequests()
return authToken
})
.catch(() => {
pendingRequests = []
// Log the user out here.
return false
})
.finally(() => {
isRefreshing = false
})
).filter(value => Boolean(value))
} else {
innerForward = fromPromise(
new Promise(resolve => {
pendingRequests.push(() => resolve())
})
)
}
return innerForward.flatMap(() => {
return forward(operation)
})
} else {
console.log(`[GraphQL error]: Message: ${firstGraphQLError.message}, Location: ${firstGraphQLError.locations}, Path: ${firstGraphQLError.path}`)
}
}
if (networkError) {
console.log(`[Network error]: ${networkError}`)
}
})
const client = new ApolloClient({
cache: new InMemoryCache(),
link: from([
errorLink,
authLink,
new HttpLink({ uri: '' })
])
})
此解决方案还处理多个并发请求,将它们排队并在刷新访问令牌后请求它们。
由于我是 Apollo Client 的新手,我觉得我的知识可能存在根本性的差距。但是我已经仔细阅读了 Stack Overflow、GitHub 问题和 Google 以找到我 运行 遇到但尚未找到任何问题的明显解决方案。
基本上我有以下 Apollo 客户端设置(简化):
const auth = new Auth()
const authMiddleware = new ApolloLink((operation, forward) => {
const authToken = auth.getToken().access_token
console.log(authToken)
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
authorization: authToken ? `Bearer ${authToken}` : ''
}
}))
return forward(operation)
})
const cache = new InMemoryCache()
const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ extensions, locations, message, path }) => {
if (extensions.code === 'access-denied') {
auth.refresh()
.then(() => {
console.log(`new access token: ${auth.getToken().access_token}`)
return forward(operation)
}).catch((error) => {
handleLogout(error)
})
}
})
}
})
const handleLogout = (reason) => {
auth.logout()
}
const httpLink = new HttpLink({ uri: '' })
const client = new ApolloClient({
cache: cache,
link: ApolloLink.from([
errorLink,
authMiddleware,
httpLink
])
})
我有一个简单的查询:
client.query({
query: Queries.MyQuery
}).then((response) => {
console.log(response)
}, (error) => {
console.log(error)
})
如果第一次运行时有有效的 OAuth 访问令牌,客户端将成功执行查询。但是,如果我使 OAuth 服务器上的访问令牌过期,然后尝试执行查询,它不会成功完成。
调试时,我可以看到发生了什么:
authMiddleware
将旧访问令牌正确添加到请求 header。- 请求失败,因为令牌不再有效。这由
errorLink
. 处理 属性
errorLink
也成功检索到新的访问令牌和 returnsforward(operation)
.authMiddleware
再次被调用,添加新的访问令牌,并且 returnsforward(operation)
.
这就是问题所在。查询从不 re-executes。如果我手动刷新页面以 re-execute 查询,它将使用新的访问令牌并成功完成。
通过阅读文档,听起来我的设置方式应该可行,但显然我做错了什么。
我能够通过挖掘各种来源拼凑出正在发生的事情。之所以令人困惑,主要是因为许多开发人员过去一直在努力解决这个问题(现在似乎仍然如此),所以那里有大量过时的解决方案和帖子。
This GitHub issue was the most useful source of information, even though it's attached to a repository that's now deprecated.
我花了一些时间沿着使用实用方法将 promise 变成 Observable 的道路前进,但如果您使用 fromPromise
.
这是我最终得到的适用于 Apollo Client 3.2.0 的解决方案:
const authLink = new ApolloLink((operation, forward) => {
const authToken = auth.getToken().access_token
console.info(`access token: ${authToken}`)
operation.setContext(({ headers }) => ({
headers: {
...headers,
authorization: authToken ? `Bearer ${authToken}` : ''
}
}))
return forward(operation)
})
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
const firstGraphQLError = graphQLErrors[0]
if (firstGraphQLError.extensions.code === 'access-denied') {
let innerForward
if (!isRefreshing) {
isRefreshing = true
innerForward = fromPromise(
auth.refresh()
.then(() => {
const authToken = auth.getToken().access_token
console.info(`access token refreshed: ${authToken}`)
resolvePendingRequests()
return authToken
})
.catch(() => {
pendingRequests = []
// Log the user out here.
return false
})
.finally(() => {
isRefreshing = false
})
).filter(value => Boolean(value))
} else {
innerForward = fromPromise(
new Promise(resolve => {
pendingRequests.push(() => resolve())
})
)
}
return innerForward.flatMap(() => {
return forward(operation)
})
} else {
console.log(`[GraphQL error]: Message: ${firstGraphQLError.message}, Location: ${firstGraphQLError.locations}, Path: ${firstGraphQLError.path}`)
}
}
if (networkError) {
console.log(`[Network error]: ${networkError}`)
}
})
const client = new ApolloClient({
cache: new InMemoryCache(),
link: from([
errorLink,
authLink,
new HttpLink({ uri: '' })
])
})
此解决方案还处理多个并发请求,将它们排队并在刷新访问令牌后请求它们。