observation-js
Version:
A fully-typed TypeScript client for the waarneming.nl API.
104 lines (103 loc) • 3.36 kB
JavaScript
import { AuthenticationError } from './errors.js';
/**
* @internal
* Interceptor that automatically refreshes access tokens when a 401 error occurs.
*/
export class RefreshTokenInterceptor {
client;
isRefreshing = false;
refreshPromise = null;
maxRetries = 1;
failedQueue = [];
constructor(client) {
this.client = client;
}
/**
* Creates the response error handler for automatic token refresh.
*/
createResponseErrorHandler() {
return async (error) => {
if (!(error instanceof AuthenticationError)) {
throw error;
}
const response = error.response;
if (!response || response.status !== 401 || !this.client.hasRefreshToken()) {
throw error;
}
const originalConfig = response.config;
if (!originalConfig || originalConfig._retry) {
throw error;
}
if (this.isRefreshing) {
// Queue the request to be retried after refresh completes
return new Promise((resolve, reject) => {
this.failedQueue.push({ resolve, reject });
});
}
originalConfig._retry = true;
this.isRefreshing = true;
try {
await this.client.refreshAccessToken();
this.processQueue(null);
return await this.retryOriginalRequest(originalConfig);
}
catch (refreshError) {
this.processQueue(refreshError);
throw error;
}
finally {
this.isRefreshing = false;
}
};
}
/**
* Process all queued requests after token refresh
*/
processQueue(error) {
this.failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
}
else {
resolve(null);
}
});
this.failedQueue = [];
}
/**
* Retries the original request with the new access token.
*/
async retryOriginalRequest(config) {
const headers = new Headers(config.headers);
if (this.client.hasAccessToken()) {
headers.set('Authorization', `Bearer ${this.client.getCurrentAccessToken()}`);
}
const url = config.url;
if (!url) {
throw new Error('Original request URL not found');
}
const newConfig = {
...config,
headers,
};
const response = await fetch(url, newConfig);
// Attach config to response for potential future retries
response.config = newConfig;
if (!response.ok) {
const body = await response.text();
let errorBody = null;
try {
errorBody = body ? JSON.parse(body) : null;
}
catch {
errorBody = body;
}
if (response.status === 401 || response.status === 403) {
throw new AuthenticationError(response, errorBody);
}
throw new Error(`Request failed with status ${response.status}`);
}
const text = await response.text();
return text ? JSON.parse(text) : {};
}
}