Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add renew token query for apollo client (chrome-extension) #5200

Merged
merged 7 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions packages/twenty-chrome-extension/src/db/auth.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,3 @@ export const exchangeAuthorizationCode = async (
return data.exchangeAuthorizationCode;
else return null;
};

// export const RenewToken = async (appToken: string): Promise<Tokens | null> => {
// const data = await callQuery<Tokens>(RENEW_TOKEN, { appToken });
// if (isDefined(data)) return data;
// else return null;
// };
34 changes: 34 additions & 0 deletions packages/twenty-chrome-extension/src/db/token.db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ApolloClient, InMemoryCache } from '@apollo/client';

import { Tokens } from '~/db/types/auth.types';
import { RENEW_TOKEN } from '~/graphql/auth/queries';
import { isDefined } from '~/utils/isDefined';

export const renewToken = async (appToken: string): Promise<Tokens | null> => {
const store = await chrome.storage.local.get();
const serverUrl = `${
isDefined(store.serverBaseUrl)
? store.serverBaseUrl
: import.meta.env.VITE_SERVER_BASE_URL
}/graphql`;

// Create new client to call refresh token graphql mutation
const client = new ApolloClient({
uri: serverUrl,
cache: new InMemoryCache({}),
});

const { data } = await client.query({
query: RENEW_TOKEN,
variables: {
appToken,
},
fetchPolicy: 'network-only',
});

if (isDefined(data)) {
return data;
} else {
return null;
}
};
38 changes: 19 additions & 19 deletions packages/twenty-chrome-extension/src/graphql/auth/queries.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// import { gql } from '@apollo/client';
import { gql } from '@apollo/client';

// export const RENEW_TOKEN = gql`
// query RenewToken($appToken: String!) {
// renewToken(appToken: $appToken) {
// loginToken {
// token
// expiresAt
// }
// accessToken {
// token
// expiresAt
// }
// refreshToken {
// token
// expiresAt
// }
// }
// }
// `;
export const RENEW_TOKEN = gql`
query RenewToken($appToken: String!) {
renewToken(appToken: $appToken) {
loginToken {
token
expiresAt
}
accessToken {
token
expiresAt
}
refreshToken {
token
expiresAt
}
}
}
`;
123 changes: 83 additions & 40 deletions packages/twenty-chrome-extension/src/utils/apolloClient.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,110 @@
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import {
ApolloClient,
from,
fromPromise,
HttpLink,
InMemoryCache,
} from '@apollo/client';
import { onError } from '@apollo/client/link/error';

import { renewToken } from '~/db/token.db';
import { Tokens } from '~/db/types/auth.types';
import { isDefined } from '~/utils/isDefined';

const clearStore = () => {
chrome.storage.local.remove('loginToken');

chrome.storage.local.remove('accessToken');

chrome.storage.local.remove('refreshToken');

chrome.storage.local.set({ isAuthenticated: false });
};

const getApolloClient = async () => {
const setStore = (tokens: Tokens) => {
chrome.storage.local.set({
loginToken: tokens.loginToken,
});
chrome.storage.local.set({
accessToken: tokens.accessToken,
});
chrome.storage.local.set({
refreshToken: tokens.refreshToken,
});
};

export const getServerUrl = async () => {
const store = await chrome.storage.local.get();
const serverUrl = `${
isDefined(store.serverBaseUrl)
? store.serverBaseUrl
: import.meta.env.VITE_SERVER_BASE_URL
}/graphql`;
return serverUrl;
};

const errorLink = onError(({ graphQLErrors, networkError }) => {
if (isDefined(graphQLErrors)) {
for (const graphQLError of graphQLErrors) {
if (graphQLError.message === 'Unauthorized') {
//TODO: replace this with renewToken mutation
clearStore();
return;
}
switch (graphQLError?.extensions?.code) {
case 'UNAUTHENTICATED': {
//TODO: replace this with renewToken mutation
clearStore();
break;
const getAuthToken = async () => {
const store = await chrome.storage.local.get();
if (isDefined(store.accessToken)) return `Bearer ${store.accessToken.token}`;
else return '';
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we return null instead to signify that we couldn't get it ? Seems that '' will create a silent error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to return null 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like HttpLink shouldn't be created if we don't have any accessToken, but instead redirect to the login page no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the plan! We currently don't have any routing in place though its next on the agenda :)


const getApolloClient = async () => {
const store = await chrome.storage.local.get();
const errorLink = onError(
({ graphQLErrors, networkError, forward, operation }) => {
if (isDefined(graphQLErrors)) {
for (const graphQLError of graphQLErrors) {
if (graphQLError.message === 'Unauthorized') {
return fromPromise(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be a problem here, because the server doesn't necessarily returns this exact error.

I'm looking into it, might be a problem of guard.

renewToken(store.refreshToken.token)
.then((tokens) => {
if (isDefined(tokens)) {
setStore(tokens);
}
})
.catch(() => {
clearStore();
}),
).flatMap(() => forward(operation));
}
switch (graphQLError?.extensions?.code) {
case 'UNAUTHENTICATED': {
return fromPromise(
renewToken(store.refreshToken.token)
.then((tokens) => {
if (isDefined(tokens)) {
setStore(tokens);
}
})
.catch(() => {
clearStore();
}),
).flatMap(() => forward(operation));
}
default:
// eslint-disable-next-line no-console
console.error(
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${
graphQLError.locations
? JSON.stringify(graphQLError.locations)
: graphQLError.locations
}, Path: ${graphQLError.path}`,
);
break;
}
default:
// eslint-disable-next-line no-console
console.error(
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${
graphQLError.locations
? JSON.stringify(graphQLError.locations)
: graphQLError.locations
}, Path: ${graphQLError.path}`,
);
break;
}
}
}

if (isDefined(networkError)) {
// eslint-disable-next-line no-console
console.error(`[Network error]: ${networkError}`);
}
});
if (isDefined(networkError)) {
// eslint-disable-next-line no-console
console.error(`[Network error]: ${networkError}`);
}
},
);

const httpLink = new HttpLink({
uri: serverUrl,
headers: isDefined(store.accessToken)
? {
Authorization: `Bearer ${store.accessToken.token}`,
}
: {},
uri: await getServerUrl(),
headers: {
Authorization: await getAuthToken(),
},
});

const client = new ApolloClient({
Expand Down
Loading