refactor: replace @ory/client built-in axios
getlarge opened this issue · 0 comments
getlarge commented
To improve error responses handling for calls to Ory APIs, it would be useful to provide a custom axios
instance.
This instance would have custom interceptors to wrap errors in standard class, and would allow to add retry strategies in case of rate-limiting.
The BaseAPI
class from @ory/client
, which all API extends, allow to pass a custom axios
instance in the constructor.
The OryPermissionsService
and OryAuthenticationService
could make use of this improvement.
In OryPermissionsModule
and OryAuthenticationModule
, import HttpModule
:
// ...
declare module 'axios' {
interface AxiosRequestConfig {
retries?: number;
retryCondition?: (error: AxiosError) => boolean;
retryDelay?: (error: AxiosError, retryCount: number) => number;
}
}
const HttpModuleWithRetry = HttpModule.register({
timeout: 5000,
responseType: 'json',
validateStatus(status: number) {
return status >= 200 && status < 300;
},
retries: 3,
retryCondition(error) {
const statusToRetry = [429];
return error.response?.status
? statusToRetry.includes(error.response?.status)
: false;
},
retryDelay(error, retryCount) {
if (error.response?.status === 429) {
const headers = error.response.headers;
const remaining = headers['x-ratelimit-remaining'];
const resetTimestamp = headers['x-ratelimit-reset'];
if (Number(remaining) === 0) {
return Number(resetTimestamp) * 1000 - Date.now();
}
}
return retryCount * 250;
},
});
// ...
In OryPermissionsService
, inject HttpService, create axios interceptors and pass custom axios instance:
constructor(
@Inject(OryPermissionsModuleOptions) options: OryPermissionsModuleOptions,
@Inject(HttpService) private readonly httpService: HttpService,
) {
this.httpService.axiosRef.interceptors.response.use(
(response) => response,
async (error) => {
if (isAxiosError(error)) {
if ('response' in error) {
const { config, response } = error;
const shouldRetry = config?.retryCondition(error) ?? true;
if (config?.retries && shouldRetry) {
const retryDelay =
config?.retryDelay(error, config.retries) ?? 250;
config.retries -= 1;
await delay(retryDelay);
return this.httpService.axiosRef(config);
}
const oryError = new OryError(error);
return Promise.reject(oryError);
}
const oryError = new OryError(error);
return Promise.reject(oryError);
}
const oryError = new OryError(error);
return Promise.reject(oryError);
},
);
const { ketoAccessToken, ketoAdminApiPath, ketoPublicApiPath } = options;
this.relationShipApi = new RelationshipApi(
new Configuration({
basePath: ketoAdminApiPath,
accessToken: ketoAccessToken,
}),
'',
this.httpService.axiosRef,
);
this.permissionApi = new PermissionApi(
new Configuration({
basePath: ketoPublicApiPath,
accessToken: ketoAccessToken,
}),
'',
this.httpService.axiosRef,
);
}