Closed kpervin closed 2 months ago
@ChuckJonas Can I get a review?
Alternatively, if we could get access to the axios instance when using setDefaultConfig
so as to add on interceptors (primarily for auth refetch), that would also be another approach.
Curious what your use case was for the default Axios instance was? If I needed to modify its behavior, I'd always just do so after setting up the default instance (via the request
property).
Your approach is probably more explicit and self documenting though
EDIT: I didn't see your latest comment when I started writing this.
You can modify the interceptors AFTER the instance is created, ~but because the default Rest client itself is not a singleton (probably should be), I guess this wouldn't really work well when using the RestObjects
~. You approach seems solid to me.
@kpervin Sorry, I should have looked at the code more closely (it's been a while since I wrote this).
The default rest instance IS a "singleton"
That means you should be able to do something like this:
setDefaultConfig(...)
const client = new Rest()
client.request.interceptors = ...
Either way, like I said before, your improvements make this process more explicit so I think they make sense
Part of the reason for this would be to add in capabilities for NestJS, such that:
@Module({
imports: [
ConfigModule.forFeature(salesforceConfig),
HttpModule.registerAsync({
imports: [ConfigModule.forFeature(salesforceConfig)],
inject: [salesforceConfig.KEY],
useFactory: (config: ConfigType<typeof salesforceConfig>) => {
return {
baseURL: config.instanceUrl,
};
},
}),
],
})
export class SalesforceModule implements OnModuleInit {
private logger = new Logger(SalesforceModule.name);
constructor(
private http: HttpService,
@Inject(salesforceConfig.KEY)
private readonly config: ConfigType<typeof salesforceConfig>,
) {}
private auth(): Promise<TokenResponse> {
return this.http.axiosRef
.post<TokenResponse>(
"/services/oauth2/token",
new URLSearchParams({
grant_type: "client_credentials",
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
}),
)
.then(({ data }) => {
setDefaultConfig({
accessToken: data.access_token,
});
return data;
})
.catch((e) => {
throw e;
});
}
public onModuleInit(): void {
this.http.axiosRef.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
// If the error is due to token expiry
if (error.response.status === 401) {
this.logger.log("Access token expired. Refreshing...");
delete this.http.axiosRef.defaults.headers.common["Authorization"]; // Remove the invalid access token
try {
// Obtain a new access token
const { access_token } = await this.auth();
originalRequest.headers["Authorization"] = `Bearer ${access_token}`;
return this.http.axiosRef.request(originalRequest); // Retry the original request with the new access token
} catch (error) {
this.logger.error("Error refreshing access token:", error);
return Promise.reject(error);
}
}
return Promise.reject(error);
},
);
setDefaultConfig({
instanceUrl: this.config.instanceUrl,
version: this.config.version,
axiosInstance: this.http.axiosRef,
});
}
}
This means that it has underlying access to all NestJS lifecycles due to the module-based approach.