aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/api')
-rw-r--r--src/client/api/client.ts58
1 files changed, 53 insertions, 5 deletions
diff --git a/src/client/api/client.ts b/src/client/api/client.ts
index 7607eb6..b39f064 100644
--- a/src/client/api/client.ts
+++ b/src/client/api/client.ts
@@ -61,12 +61,50 @@ export function createClient(baseUrl: string): Client {
export class ApiClient {
private tokenStorage: TokenStorage;
private refreshPromise: Promise<boolean> | null = null;
+ private baseUrl: string;
public readonly rpc: Client;
constructor(options: ApiClientOptions = {}) {
- const baseUrl = options.baseUrl ?? window.location.origin;
+ this.baseUrl = options.baseUrl ?? window.location.origin;
this.tokenStorage = options.tokenStorage ?? localStorageTokenStorage;
- this.rpc = createClient(baseUrl);
+ this.rpc = this.createAuthenticatedClient();
+ }
+
+ private createAuthenticatedClient(): Client {
+ return hc<AppType>(this.baseUrl, {
+ fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
+ return this.authenticatedFetch(input, init);
+ },
+ });
+ }
+
+ async authenticatedFetch(
+ input: RequestInfo | URL,
+ init?: RequestInit,
+ ): Promise<Response> {
+ const tokens = this.tokenStorage.getTokens();
+ const headers = new Headers(init?.headers);
+
+ if (tokens?.accessToken && !headers.has("Authorization")) {
+ headers.set("Authorization", `Bearer ${tokens.accessToken}`);
+ }
+
+ const response = await fetch(input, { ...init, headers });
+
+ if (response.status === 401 && tokens?.refreshToken) {
+ // Try to refresh the token
+ const refreshed = await this.refreshToken();
+ if (refreshed) {
+ // Retry with new token
+ const newTokens = this.tokenStorage.getTokens();
+ if (newTokens?.accessToken) {
+ headers.set("Authorization", `Bearer ${newTokens.accessToken}`);
+ }
+ return fetch(input, { ...init, headers });
+ }
+ }
+
+ return response;
}
private async handleResponse<T>(response: Response): Promise<T> {
@@ -108,15 +146,25 @@ export class ApiClient {
}
try {
- const res = await this.rpc.api.auth.refresh.$post({
- json: { refreshToken: tokens.refreshToken },
+ // Use raw fetch to avoid infinite loop through authenticatedFetch
+ const res = await fetch(`${this.baseUrl}/api/auth/refresh`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ refreshToken: tokens.refreshToken }),
});
if (!res.ok) {
+ // Clear tokens if refresh fails
+ this.tokenStorage.clearTokens();
return false;
}
- const data = await res.json();
+ const data = (await res.json()) as {
+ accessToken: string;
+ refreshToken: string;
+ };
this.tokenStorage.setTokens({
accessToken: data.accessToken,
refreshToken: data.refreshToken,