@donation-alerts/api
Version:
Interact with Donation Alerts API.
173 lines (172 loc) • 7.2 kB
JavaScript
import { __decorate } from "tslib";
import { TimeBasedRateLimiter, TimedPassthruRateLimiter, } from '@d-fischer/rate-limiter';
import { callDonationAlertsApi, callDonationAlertsApiRaw, handleDonationAlertsApiResponseError, transformDonationAlertsResponse, } from '@donation-alerts/api-call';
import { extractUserId, ReadDocumentation } from '@donation-alerts/common';
import { createLogger } from '@stimulcross/logger';
import { nonenumerable } from '@stimulcross/shared-utils';
import { Memoize } from 'typescript-memoize';
import { DonationAlertsCentrifugoApi } from "./api/centrifugo/donation-alerts-centrifugo-api.mjs";
import { DonationAlertsCustomAlertsApi } from "./api/customAlerts/donation-alerts-custom-alerts-api.mjs";
import { DonationAlertsDonationsApi } from "./api/donations/donation-alerts-donations-api.mjs";
import { DonationAlertsMerchandiseApi } from "./api/merchandise/donation-alerts-merchandise-api.mjs";
import { DonationAlertsUsersApi } from "./api/users/donation-alerts-users-api.mjs";
/**
* The client for interacting with the Donation Alerts API.
*/
let ApiClient = class ApiClient {
/**
* Creates a new instance of the API client.
*
* @param config The configuration options for the API client.
* @throws Error if the `authProvider` is not supplied in the configuration.
*/
constructor(config) {
if (!config.authProvider) {
throw new Error('No auth provider given. Please supply the `authProvider` option.');
}
this._config = config;
this._limitReachedBehavior = config.rateLimiterOptions?.limitReachedBehavior ?? 'enqueue';
if (config.rateLimiterOptions?.limitToOneRequestPerSecond ?? true) {
this._rateLimiter = new TimedPassthruRateLimiter(new TimeBasedRateLimiter({
timeFrame: 1000,
bucketSize: 1,
doRequest: async (req) => await callDonationAlertsApiRaw(req.options, req.accessToken, req.fetchOptions),
logger: { minLevel: 'ERROR' },
}), { bucketSize: 60, timeFrame: 60000 });
}
else {
this._rateLimiter = new TimeBasedRateLimiter({
bucketSize: 60,
timeFrame: 60000,
doRequest: async (req) => await callDonationAlertsApiRaw(req.options, req.accessToken, req.fetchOptions),
logger: { minLevel: 'ERROR' },
});
}
this._logger = createLogger({ context: 'da:api', ...config.logger });
}
/**
* Users API namespace.
*
* This namespace allows you to fetch user details, such as information about the authenticated user.
*/
get users() {
return new DonationAlertsUsersApi(this);
}
/**
* Donations API namespace.
*
* This namespace provides methods for retrieving donation data.
*/
get donations() {
return new DonationAlertsDonationsApi(this);
}
/**
* Custom Alerts API namespace.
*
* This namespace provides methods for sending custom alerts.
*/
get customAlerts() {
return new DonationAlertsCustomAlertsApi(this);
}
/**
*
* Centrifugo API namespace.
*
* This namespace provides methods for subscribing to Centrifugo channels.
*/
get centrifugo() {
return new DonationAlertsCentrifugoApi(this);
}
/**
* Merchandise API namespace.
*
* This namespace allows managing merchandise-related data.
*/
get merchandise() {
return new DonationAlertsMerchandiseApi(this);
}
/**
* Sends a request to the Donation Alerts API.
*
* @param user The ID of the user making the request.
* @param options Options for the API call, including method, URL, and other details.
* @param rateLimiterOptions Options for fine-tuning rate-limiting behavior.
*
* @throws {@link HttpError} If the response status code is outside the 200-299 range.
* @throws {@link UnregisteredUserError} If the specified user is not registered in the authentication provider.
* @throws {@link MissingScopeError} If the access token lacks the required scope to complete the request.
*/
async callApi(user, options, rateLimiterOptions = {}) {
const userId = extractUserId(user);
const { authProvider } = this._config;
const shouldAuth = options.auth ?? true;
let accessToken = shouldAuth
? await authProvider.getAccessTokenForUser(userId, options.scope ? [options.scope] : undefined)
: null;
if (!accessToken) {
return await callDonationAlertsApi(options, undefined, this._config.fetchOptions);
}
let response = await this._callApiInternal(options, accessToken.accessToken, rateLimiterOptions);
if (response.status === 401 && authProvider.refreshAccessTokenForUser) {
accessToken = await authProvider.refreshAccessTokenForUser(userId);
response = await this._callApiInternal(options, accessToken.accessToken, rateLimiterOptions);
}
await handleDonationAlertsApiResponseError(response, options);
return await transformDonationAlertsResponse(response);
}
async _callApiInternal(options, accessToken, rateLimiterOptions = {}) {
const { fetchOptions } = this._config;
const type = options.type ?? 'api';
const limitReachedBehavior = rateLimiterOptions.limitReachedBehavior ?? this._limitReachedBehavior;
this._logger.debug(`Calling ${type}: ${options.method ?? 'GET'} ${options.url}`);
if (options.query) {
this._logger.trace(`Query: ${JSON.stringify(options.query)}`);
}
if (options.jsonBody) {
this._logger.trace(`Request JSON body: ${JSON.stringify(options.jsonBody)}`);
}
if (options.formBody) {
this._logger.trace(`Request form body: ${JSON.stringify(options.formBody)}`);
}
const response = type === 'api'
? await this._rateLimiter.request({
options,
accessToken,
fetchOptions,
}, { limitReachedBehavior })
: await callDonationAlertsApiRaw(options, accessToken, fetchOptions);
this._logger.debug(`Called API: ${options.method ?? 'GET'} ${options.url} - result: ${response.status}`);
return response;
}
};
__decorate([
nonenumerable
], ApiClient.prototype, "_config", void 0);
__decorate([
nonenumerable
], ApiClient.prototype, "_logger", void 0);
__decorate([
nonenumerable
], ApiClient.prototype, "_rateLimiter", void 0);
__decorate([
nonenumerable
], ApiClient.prototype, "_limitReachedBehavior", void 0);
__decorate([
Memoize()
], ApiClient.prototype, "users", null);
__decorate([
Memoize()
], ApiClient.prototype, "donations", null);
__decorate([
Memoize()
], ApiClient.prototype, "customAlerts", null);
__decorate([
Memoize()
], ApiClient.prototype, "centrifugo", null);
__decorate([
Memoize()
], ApiClient.prototype, "merchandise", null);
ApiClient = __decorate([
ReadDocumentation('api')
], ApiClient);
export { ApiClient };