ChuckJonas / ts-force

A Salesforce REST Client written in Typescript for Typescript
88 stars 21 forks source link

feat: added ability to set default axios instance for all rest instances #156

Closed kpervin closed 2 months ago

kpervin commented 7 months ago
kpervin commented 2 months ago

@ChuckJonas Can I get a review?

kpervin commented 2 months ago

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.

ChuckJonas commented 2 months ago

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.

ChuckJonas commented 2 months ago

@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

kpervin commented 2 months ago

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.