mirror of
https://github.com/muerwre/muerwre.github.io.git
synced 2025-04-25 10:56:40 +07:00
added whole content
This commit is contained in:
parent
07a306a6f4
commit
dd104eed49
70 changed files with 5962 additions and 19 deletions
157
content/GraphQL/Refresh token in Apollo client.md
Normal file
157
content/GraphQL/Refresh token in Apollo client.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
If your GraphQL api needs token refresh option, you can pass custom fetch function for Apollo Client.
|
||||
|
||||
```typescript
|
||||
export const createApolloClient = (
|
||||
url: string,
|
||||
logout: () => void,
|
||||
getAuthorizationData: () => { authorization: string },
|
||||
refreshToken: () => Promise<
|
||||
{ accessToken: string; refreshToken: string } | undefined
|
||||
>,
|
||||
) =>
|
||||
new ApolloClientBase({
|
||||
// ...other options
|
||||
link: ApolloLink.from([
|
||||
// ...other options
|
||||
setContext(async (_, { headers }) => {
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
...getAuthorizationData(),
|
||||
},
|
||||
};
|
||||
}),
|
||||
new HttpLink({
|
||||
uri: url,
|
||||
fetch: fetchWithTokenRefresh(logout, refreshToken),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
```
|
||||
|
||||
Custom fetch function for this request. You should tune `hasUnauthorizedError` and
|
||||
`isRefreshRequestOptions` to match your api.
|
||||
|
||||
```typescript
|
||||
/** Global singleton for refreshing promise */
|
||||
let refreshingPromise: Promise<string> | null = null;
|
||||
|
||||
/** Checks if GraphQl errors has unauthenticated error */
|
||||
const hasUnauthorizedError = (errors: Array<{ code?: ErrorCode }>): boolean =>
|
||||
Array.isArray(errors) &&
|
||||
errors.some(error => {
|
||||
return error.status === 401; // Distinguish unauthorized error here
|
||||
});
|
||||
|
||||
/** Detects if customFetch is sending refresh request */
|
||||
const isRefreshRequestOptions = (options: RequestInit) => {
|
||||
try {
|
||||
const body = JSON.parse(options?.body as string);
|
||||
return body.operationName === 'RefreshToken';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/** fetchWithTokenRefresh is a custom fetch function with token refresh for apollo */
|
||||
export const fetchWithTokenRefresh =
|
||||
(
|
||||
logout: () => void,
|
||||
refreshToken: () => Promise<
|
||||
{ accessToken: string; refreshToken: string } | undefined
|
||||
>,
|
||||
) =>
|
||||
async (uri: string, options: RequestInit): Promise<Response> => {
|
||||
// already refreshing token, wait for it and then use refreshed token
|
||||
// or use empty authorization if refreshing failed
|
||||
if (
|
||||
!isRefreshRequestOptions(options) &&
|
||||
refreshingPromise &&
|
||||
(options.headers as Record<string, string>)?.authorization
|
||||
) {
|
||||
const newAccessToken = await refreshingPromise
|
||||
.catch(() => {
|
||||
// refreshing token from other request failed, retry without authorization
|
||||
return '';
|
||||
});
|
||||
|
||||
options.headers = {
|
||||
...(options.headers || {}),
|
||||
authorization: newAccessToken,
|
||||
};
|
||||
}
|
||||
|
||||
return fetch(uri, options).then(async response => {
|
||||
const text = await response.text();
|
||||
const json = JSON.parse(text);
|
||||
|
||||
// check for unauthorized errors, if not present, just return result
|
||||
if (
|
||||
isRefreshRequestOptions(options) ||
|
||||
!json?.errors ||
|
||||
!Array.isArray(json.errors) ||
|
||||
!hasUnauthorizedError(json.errors)
|
||||
) {
|
||||
return {
|
||||
...response,
|
||||
ok: true,
|
||||
json: async () =>
|
||||
new Promise<unknown>(resolve => {
|
||||
resolve(json);
|
||||
}),
|
||||
text: async () =>
|
||||
new Promise<string>(resolve => {
|
||||
resolve(text);
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// If unauthorized, refresh token and try again
|
||||
if (!refreshingPromise) {
|
||||
refreshingPromise = refreshToken()
|
||||
.then(async (tokens): Promise<string> => {
|
||||
refreshingPromise = null;
|
||||
|
||||
if (!tokens?.accessToken) {
|
||||
throw new Error('Session expired');
|
||||
}
|
||||
|
||||
return tokens?.accessToken;
|
||||
})
|
||||
.catch(() => {
|
||||
refreshingPromise = null;
|
||||
|
||||
// can't refresh token. logging out
|
||||
logout();
|
||||
throw new Error('Session expired');
|
||||
});
|
||||
}
|
||||
|
||||
// success or any non-auth error
|
||||
return refreshingPromise
|
||||
.then(async (newAccessToken: string) => {
|
||||
// wait for other request's refreshing query to finish, when retry
|
||||
|
||||
return fetch(uri, {
|
||||
...options,
|
||||
headers: {
|
||||
...(options.headers || {}),
|
||||
authorization: newAccessToken,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(async () => {
|
||||
// refreshing token from other request failed, retry without authorization
|
||||
|
||||
return fetch(uri, {
|
||||
...options,
|
||||
headers: {
|
||||
...(options.headers || {}),
|
||||
authorization: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue