vapor
vapor14mo ago

Middleware-ish for client

Hi! I had a quick question about client-side usage: I have users with long-running sessions, and if they tokens expire the backend returns a 401. In the old codebase I'm migrating from there was a check force a token refresh if certain parameters were met, such as status === 401, route is one of x, etc. Is there a way to handle that on the @trpc/client ? something just like: for any requests, if returned status is 401, and route is one of x, force a token refresh? Thanks!
2 Replies
Dani;
Dani;14mo ago
Something like this?
export function isTRPCClientError(
cause: unknown
): cause is TRPCClientError<AppRouter> {
return cause instanceof TRPCClientError;
}
const TRPC_ENDPOINT = "http://localhost:2021/trpc";

const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpLink({
async headers() {
return {
Authorization: await getAuthorizationHeader(),
};
},
url: TRPC_ENDPOINT,
async fetch(input: RequestInfo | URL, init?: RequestInit) {
try {
const response = await fetch(input, init);
// RPC errors are not thrown automatically, so we'll throw them manually and handle them in the catch block
if (response.statusText !== "OK") {
const error = await response.json();
throw new TRPCClientError(error.message, {
cause: error,
result: error,
});
}

return response;
} catch (error) {
if (isTRPCClientError(error)) {
const isUnauthorized = error?.data?.httpStatus === 401;

const isAuthRouter = new URL(input.toString()).pathname.startsWith(
"/trpc/auth"
);

// shouldn't retry when auth procedures fail, otherwise we might end up in an infinite loop
if (isUnauthorized && !isAuthRouter) {
// retrieve a new access token
const { token } = await trpc.auth.refreshToken.query();
setToken(token);

// init object still contains the previous auth token, we have to override it
init = {
...init,
headers: {
...init?.headers,
Authorization: await getAuthorizationHeader(),
},
};
return await fetch(input, init);
}
}
throw error;
}
},
}),
],
});
export function isTRPCClientError(
cause: unknown
): cause is TRPCClientError<AppRouter> {
return cause instanceof TRPCClientError;
}
const TRPC_ENDPOINT = "http://localhost:2021/trpc";

const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpLink({
async headers() {
return {
Authorization: await getAuthorizationHeader(),
};
},
url: TRPC_ENDPOINT,
async fetch(input: RequestInfo | URL, init?: RequestInit) {
try {
const response = await fetch(input, init);
// RPC errors are not thrown automatically, so we'll throw them manually and handle them in the catch block
if (response.statusText !== "OK") {
const error = await response.json();
throw new TRPCClientError(error.message, {
cause: error,
result: error,
});
}

return response;
} catch (error) {
if (isTRPCClientError(error)) {
const isUnauthorized = error?.data?.httpStatus === 401;

const isAuthRouter = new URL(input.toString()).pathname.startsWith(
"/trpc/auth"
);

// shouldn't retry when auth procedures fail, otherwise we might end up in an infinite loop
if (isUnauthorized && !isAuthRouter) {
// retrieve a new access token
const { token } = await trpc.auth.refreshToken.query();
setToken(token);

// init object still contains the previous auth token, we have to override it
init = {
...init,
headers: {
...init?.headers,
Authorization: await getAuthorizationHeader(),
},
};
return await fetch(input, init);
}
}
throw error;
}
},
}),
],
});
vapor
vapor14mo ago
Awesome thanks !