@backstage/integration
Version:
Helpers for managing integrations towards external systems
130 lines (127 loc) • 3.82 kB
JavaScript
import { basicIntegrations, defaultScmResolveUrl } from '../helpers.esm.js';
import { readGitLabIntegrationConfigs } from './config.esm.js';
import pThrottle from 'p-throttle';
class GitLabIntegration {
constructor(integrationConfig) {
this.integrationConfig = integrationConfig;
this.fetchImpl = this.createFetchStrategy();
}
static factory = ({ config }) => {
const configs = readGitLabIntegrationConfigs(
config.getOptionalConfigArray("integrations.gitlab") ?? []
);
return basicIntegrations(
configs.map((c) => new GitLabIntegration(c)),
(i) => i.config.host
);
};
fetchImpl;
get type() {
return "gitlab";
}
get title() {
return this.integrationConfig.host;
}
get config() {
return this.integrationConfig;
}
resolveUrl(options) {
return defaultScmResolveUrl(options);
}
resolveEditUrl(url) {
return replaceGitLabUrlType(url, "edit");
}
fetch(input, init) {
return this.fetchImpl(input, init);
}
createFetchStrategy() {
let fetchFn = (url, options) => fetch(url, options);
const retryConfig = this.integrationConfig.retry;
if (retryConfig) {
fetchFn = this.withRetry(fetchFn, retryConfig);
if (retryConfig.maxApiRequestsPerMinute && retryConfig.maxApiRequestsPerMinute > 0) {
fetchFn = pThrottle({
limit: retryConfig.maxApiRequestsPerMinute,
interval: 6e4
})(fetchFn);
}
}
return fetchFn;
}
withRetry(fetchFn, retryConfig) {
const maxRetries = retryConfig?.maxRetries ?? 0;
const retryStatusCodes = retryConfig?.retryStatusCodes ?? [];
if (maxRetries <= 0 || retryStatusCodes.length === 0) {
return fetchFn;
}
const backoffDelay = (a) => Math.min(100 * Math.pow(2, a - 1), 1e4);
return async (url, options) => {
const abortSignal = options?.signal;
let attempt = 0;
for (; ; ) {
let response;
try {
response = await fetchFn(url, options);
} catch (e) {
if (abortSignal?.aborted) throw e;
if (attempt++ >= maxRetries) throw e;
await sleep(backoffDelay(attempt), abortSignal);
if (abortSignal?.aborted) throw e;
continue;
}
if (!retryStatusCodes.includes(response.status)) {
return response;
}
if (attempt++ >= maxRetries) {
return response;
}
const delay = parseRetryAfterMs(
response.headers.get("Retry-After"),
backoffDelay(attempt)
);
await response.body?.cancel().catch(() => {
});
await sleep(delay, abortSignal);
if (abortSignal?.aborted) return response;
}
};
}
}
function parseRetryAfterMs(headerValue, fallbackMs) {
if (!headerValue) {
return fallbackMs;
}
if (/^\d+$/.test(headerValue)) {
return Number(headerValue) * 1e3;
}
if (headerValue.includes(",")) {
const dateMs = Date.parse(headerValue);
if (Number.isFinite(dateMs)) {
const deltaMs = dateMs - Date.now();
return deltaMs > 0 ? deltaMs : 0;
}
}
return fallbackMs;
}
async function sleep(durationMs, abortSignal) {
if (abortSignal?.aborted) {
return;
}
await new Promise((resolve) => {
let timeoutHandle = void 0;
const done = () => {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
abortSignal?.removeEventListener("abort", done);
resolve();
};
timeoutHandle = setTimeout(done, durationMs);
abortSignal?.addEventListener("abort", done);
});
}
function replaceGitLabUrlType(url, type) {
return url.replace(/\/\-\/(blob|tree|edit)\//, `/-/${type}/`);
}
export { GitLabIntegration, parseRetryAfterMs, replaceGitLabUrlType, sleep };
//# sourceMappingURL=GitLabIntegration.esm.js.map