Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import parseJwt from "@hey/helpers/parseJwt";
2import { RefreshDocument, type RefreshMutation } from "@hey/indexer";
3import apolloClient from "@hey/indexer/apollo/client";
4import type { JwtPayload } from "@hey/types/jwt";
5import { signIn, signOut } from "@/store/persisted/useAuthStore";
6
7let refreshPromise: Promise<string> | null = null;
8const MAX_RETRIES = 5;
9
10const executeTokenRefresh = async (refreshToken: string): Promise<string> => {
11 try {
12 for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
13 const { data } = await apolloClient.mutate<RefreshMutation>({
14 mutation: RefreshDocument,
15 variables: { request: { refreshToken } }
16 });
17
18 const refreshResult = data?.refresh;
19
20 if (!refreshResult) {
21 throw new Error("No response from refresh");
22 }
23
24 if (refreshResult.__typename === "AuthenticationTokens") {
25 const { accessToken: newAccessToken, refreshToken: newRefreshToken } =
26 refreshResult;
27
28 if (!newAccessToken || !newRefreshToken) {
29 throw new Error("Missing tokens in refresh response");
30 }
31
32 signIn({
33 accessToken: newAccessToken,
34 refreshToken: newRefreshToken
35 });
36
37 return newAccessToken;
38 }
39
40 if (refreshResult.__typename === "ForbiddenError") {
41 signOut();
42 throw new Error("Refresh token is invalid or expired");
43 }
44
45 if (attempt < MAX_RETRIES - 1) {
46 await new Promise((resolve) =>
47 setTimeout(resolve, 2 ** attempt * 1000)
48 );
49 }
50 }
51
52 throw new Error("Unknown error during token refresh");
53 } finally {
54 refreshPromise = null;
55 }
56};
57
58export const refreshTokens = (refreshToken: string): Promise<string> => {
59 if (!refreshPromise) {
60 refreshPromise = executeTokenRefresh(refreshToken);
61 }
62
63 return refreshPromise;
64};
65
66export const isTokenExpiringSoon = (accessToken: string | null): boolean => {
67 if (!accessToken) {
68 return false;
69 }
70
71 const tokenData: JwtPayload = parseJwt(accessToken);
72 const bufferInMinutes = 5;
73 return (
74 !!tokenData.exp &&
75 Date.now() >= tokenData.exp * 1000 - bufferInMinutes * 60 * 1000
76 );
77};