Я создаю поток аутентификации с двумя токенами (accessToken, refreshToken) на react-native/apollo-hooks/graphql. И создал apollo-hook для updateTokens. И этот хук мне нужно использовать в разных компонентах, потому что каждый раз, когда пользователь хочет выполнить какие-либо действия, такие как создание публикации или комментария, мне нужно вызвать хук updateTokens apollo. Поэтому мне нужно вынести всю логику updateTokens в отдельный utils, потому что я не хочу повторяться и ставить везде один и тот же код. Но когда я делаю отдельный файл утилиты с хуком аполлона, он не работает. Может кто-нибудь объяснить мне, как я могу это сделать, пожалуйста!
import React from 'react'
import { View, Button, Text } from 'react-native'
import { useMutation } from 'react-apollo-hooks'
import gql from 'graphql-tag'
import * as Keychain from 'react-native-keychain'
const UPDATE_TOKENS = gql`
mutation UpdateTokens($accessToken: String!, $refreshToken: String!) {
updateTokens(data: { accessToken: $accessToken, refreshToken: $refreshToken }) {
user {
name
phone
}
accessToken
refreshToken
}
}
`
const HomeScreen = ({ navigation }) => {
const update_tokens = useMutation(UPDATE_TOKENS)
const updateTokens = (accessToken, refreshToken) => {
console.info('accessToken', accessToken)
console.info('refreshToken', refreshToken)
update_tokens({
variables: { accessToken, refreshToken },
update: async (cache, { data }) => {
const accessToken = data.updateTokens.accessToken
const refreshToken = data.updateTokens.refreshToken
await Keychain.setGenericPassword(accessToken, refreshToken)
}
}).then(() => console.info('We have new credentials'))
}
const getCredentials = async () => {
const tokens = await Keychain.getGenericPassword()
console.info('tokens', tokens)
const keyChainAccessToken = tokens.username
const keyChainRefreshToken = tokens.password
console.info('keyChainAccessToken', keyChainAccessToken)
console.info('keyChainRefreshToken', keyChainRefreshToken)
updateTokens(keyChainAccessToken, keyChainRefreshToken)
}
return (
<View style = {{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button title = "updateTokens" onPress = {getCredentials} />
<Button title = "getCredentials" onPress = {checkCredentials} />
<Button title = "SignOut" onPress = {userSignOut} />
</View>
)
}
export { HomeScreen }
Могу ли я поместить его в отдельный файл, как этот, и использовать его в разных местах:
import { useMutation } from 'react-apollo-hooks'
import gql from 'graphql-tag'
import * as Keychain from 'react-native-keychain'
import jwtDecode from 'jwt-decode'
const UPDATE_TOKENS = gql`
mutation UpdateTokens($refreshToken: String!, $refreshTokenId: String!) {
updateTokens(refreshToken: $refreshToken, refreshTokenId: $refreshTokenId) {
user {
name
phone
}
accessToken
refreshToken
}
}
`
const updateCredentials = () => {
const update_tokens = useMutation(UPDATE_TOKENS)
const updateTokens = (refreshToken, refreshTokenId) => {
console.info('refreshToken', refreshToken)
console.info('refreshTokenId', refreshTokenId)
update_tokens({
variables: { refreshToken, refreshTokenId },
update: async (cache, { data }) => {
const newAccessToken = data.updateTokens.accessToken
const newRefreshToken = data.updateTokens.refreshToken
const user = data.updateTokens.user
await Keychain.setGenericPassword(newAccessToken, newRefreshToken)
}
}).then(() => console.info('We have new credentials'))
}
const getCredentials = async () => {
try {
const tokens = await Keychain.getGenericPassword()
console.info('tokens', tokens)
const keychainAccessToken = tokens.username
const keychainRefreshToken = tokens.password
const currentTime = Date.now() / 1000
const decodeAccessToken = jwtDecode(keychainAccessToken)
console.info('decodeAccessToken', decodeAccessToken)
const keychainRefreshTokenId = decodeAccessToken.refreshTokenId
console.info('keychainRefreshTokenId', keychainRefreshTokenId)
if (decodeAccessToken.exp > currentTime) {
console.info('Credentials is still valid')
} else if (decodeAccessToken.exp < currentTime) {
console.info('Go to Update!')
updateTokens(keychainRefreshToken, keychainRefreshTokenId)
}
} catch (err) {
throw new Error('Invalid credentials')
}
}
}
export { updateCredentials }





Для меня я не буду делать это для обновления токена. Я сделал промежуточное программное обеспечение для него.
Как установить промежуточное ПО:
import { createUploadLink } from 'apollo-upload-client';
const getClient = ({ userStore: { token, refreshToken: refresh_token }, lang }, dispatch) => {
const locale = lang || 'en';
const uploadLink = createUploadLink({
uri: 'http://localhost:4000'
});
const client = new ApolloClient({
link: ApolloLink.from([
getTokensMiddleware(token, refresh_token, locale, dispatch),
uploadLink
]),
cache: new InMemoryCache()
});
return client;
};
Промежуточное ПО:
const getTokensMiddleware = (token, refresh_token, locale, dispatch) => {
return setContext(async (req, { headers, ...others }) => {
if (!token || !refresh_token) return {};
var decoded = jwtDecode(token);
const isExpired = decoded.exp <= Date.now() / 1000 + 120;
var decodedRefresh = jwtDecode(refresh_token);
const isRefreshJWTExpired = decodedRefresh.exp <= Date.now() / 1000;
if (isRefreshJWTExpired) return {};
if (!isExpired) {
return {
...others,
headers: {
...headers,
Authorization: token ? `Bearer ${token}` : '',
locale
}
};
}
return new Promise((success, fail) => {
refreshToken(refresh_token)
.then(response => {
if (response.ok) {
return response.json();
} else {
fail(response);
}
})
.then(json => {
const { token } = json.data.refreshToken;
dispatch({type: "login", payload: json.data.refreshToken})
success({
...others,
headers: {
...headers,
Authorization: token ? `Bearer ${token}` : '',
locale
}
});
});
});
});
};
Ваша функция обновления:
const refreshToken = refreshToken => {
const data = {
operation: 'RefreshTokenMutation',
query:
'mutation RefreshTokenMutation($email: String!, $refreshToken: String!) { refreshToken(email: $email, refreshToken: $refreshToken) { token refreshToken user { id email username displayname role { title permissions __typename } __typename } __typename }}',
variables: { email: 'admin_email', refreshToken: refreshToken }
};
return fetch('http://localhost:4000/', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(data)
});
};
Как это использовать
const AppPage = () => {
const { state, dispatch } = useStore(); //a hook for getting context containing useReducer
return (
<I18nextProvider i18n = {i18n}>
<Helmet>
<title>My Project</title>
<meta name = "description" content = "My project" />
</Helmet>
<ApolloProvider client = {getClient(state, dispatch)}>
<LocaleProvider locale = {state.antdLocale}>
<App />
</LocaleProvider>
</ApolloProvider>
</I18nextProvider>
);
};
эти переменные предназначены только для моего собственного использования, вы можете использовать их по-своему. Они поступают из хука useReducer для получения и обновления состояний токена.
Не могли бы вы ответить, пожалуйста, еще на один вопрос. Когда работает Apollo setContext? В момент инициализации приложения или позже? Потому что в моем случае мне нужно сохранить токен в setContext, но когда приложение инициализируется, в хранилище связки ключей нет токенов, потому что пользователь еще не зарегистрировался. Он получает токены только после входа в систему. И когда он вышел из системы, токены стираются. Но я считаю, что setContext начинает работать с самого начала. Это правильно?
getTokensMiddleware — это функция для создания промежуточного программного обеспечения. Это начинается, когда вы устанавливаете ApolloClient. А getClient — это функция, генерирующая клиент apollo. Мы используем токены (состояние) для создания нового клиента во время работы приложения. Как только вы обновите токены (состояние), он сгенерирует новый клиент. Обновлен ответ для примера.
Где вы храните хук useReducer? Можете ли вы прислать мне пример, пожалуйста
мой код слишком ленив, чтобы в реальном случае вы могли подумать о редукции или другом инструменте управления состоянием
Но я не понимаю, как я могу создать поля в userInitialState в user.js с помощью async/await? Связка ключей работает только с async/await
Вы можете указать пустую строку, а затем обновить ее с помощью «отправки».
если вы все еще хотите это сделать, вы можете сделать это в режиме HOC.
import React from 'react';
import YOUR_FUNCTIONS from '~/functions_location';
const withRefreshToken = () => (Component) => {
return <Component {...YOUR_FUNCTIONS} />
};
export {
withRefreshToken
}
после этого вы можете сделать это так:
export default withRefreshToken(HomeScreen);
Большое спасибо за отличный ответ, но не могли бы вы объяснить, откуда вы берете эти аргументы при создании промежуточного программного обеспечения: { userStore: { token, refreshToken: refresh_token }, lang }, dispatch? В моем случае это должно быть хранилище связки ключей?