@segment/analytics-react-native
Version:
The hassle-free way to add Segment analytics to your React-Native app.
245 lines (220 loc) • 7.71 kB
text/typescript
import type {
RateLimitConfig,
BackoffConfig,
HttpConfig,
SegmentAPISettings,
SegmentAPIIntegrations,
LoggerType,
DeepPartial,
} from './types';
import { defaultHttpConfig } from './constants';
export const validateRateLimitConfig = (
config: RateLimitConfig,
logger?: LoggerType
): RateLimitConfig => {
const validated = { ...config };
if (validated.maxRetryInterval < 0.1) {
logger?.warn(
`maxRetryInterval ${validated.maxRetryInterval}s clamped to 0.1s`
);
validated.maxRetryInterval = 0.1;
} else if (validated.maxRetryInterval > 86400) {
logger?.warn(
`maxRetryInterval ${validated.maxRetryInterval}s clamped to 86400s`
);
validated.maxRetryInterval = 86400;
}
if (validated.maxRateLimitDuration < 60) {
logger?.warn(
`maxRateLimitDuration ${validated.maxRateLimitDuration}s clamped to 60s`
);
validated.maxRateLimitDuration = 60;
} else if (validated.maxRateLimitDuration > 604800) {
logger?.warn(
`maxRateLimitDuration ${validated.maxRateLimitDuration}s clamped to 604800s`
);
validated.maxRateLimitDuration = 604800;
}
if (validated.maxRetryCount < 1) {
logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 1`);
validated.maxRetryCount = 1;
} else if (validated.maxRetryCount > 100) {
logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 100`);
validated.maxRetryCount = 100;
}
// Relational: maxRateLimitDuration >= 2x maxRetryInterval
const minRateLimitDuration = validated.maxRetryInterval * 2;
if (validated.maxRateLimitDuration < minRateLimitDuration) {
logger?.warn(
`maxRateLimitDuration ${validated.maxRateLimitDuration}s clamped to ${minRateLimitDuration}s (2x maxRetryInterval)`
);
validated.maxRateLimitDuration = minRateLimitDuration;
}
return validated;
};
export const validateBackoffConfig = (
config: BackoffConfig,
logger?: LoggerType,
rateLimitConfig?: RateLimitConfig
): BackoffConfig => {
const validated = { ...config };
if (validated.maxBackoffInterval < 0.1) {
logger?.warn(
`maxBackoffInterval ${validated.maxBackoffInterval}s clamped to 0.1s`
);
validated.maxBackoffInterval = 0.1;
} else if (validated.maxBackoffInterval > 86400) {
logger?.warn(
`maxBackoffInterval ${validated.maxBackoffInterval}s clamped to 86400s`
);
validated.maxBackoffInterval = 86400;
}
if (validated.baseBackoffInterval < 0.1) {
logger?.warn(
`baseBackoffInterval ${validated.baseBackoffInterval}s clamped to 0.1s`
);
validated.baseBackoffInterval = 0.1;
} else if (validated.baseBackoffInterval > 300) {
logger?.warn(
`baseBackoffInterval ${validated.baseBackoffInterval}s clamped to 300s`
);
validated.baseBackoffInterval = 300;
}
if (validated.maxTotalBackoffDuration < 60) {
logger?.warn(
`maxTotalBackoffDuration ${validated.maxTotalBackoffDuration}s clamped to 60s`
);
validated.maxTotalBackoffDuration = 60;
} else if (validated.maxTotalBackoffDuration > 604800) {
logger?.warn(
`maxTotalBackoffDuration ${validated.maxTotalBackoffDuration}s clamped to 604800s`
);
validated.maxTotalBackoffDuration = 604800;
}
if (validated.jitterPercent < 0) {
logger?.warn(`jitterPercent ${validated.jitterPercent} clamped to 0`);
validated.jitterPercent = 0;
} else if (validated.jitterPercent > 100) {
logger?.warn(`jitterPercent ${validated.jitterPercent} clamped to 100`);
validated.jitterPercent = 100;
}
if (validated.maxRetryCount < 1) {
logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 1`);
validated.maxRetryCount = 1;
} else if (validated.maxRetryCount > 100) {
logger?.warn(`maxRetryCount ${validated.maxRetryCount} clamped to 100`);
validated.maxRetryCount = 100;
}
// Relational: baseBackoffInterval <= maxBackoffInterval
if (validated.baseBackoffInterval > validated.maxBackoffInterval) {
logger?.warn(
`baseBackoffInterval ${validated.baseBackoffInterval}s clamped to maxBackoffInterval ${validated.maxBackoffInterval}s`
);
validated.baseBackoffInterval = validated.maxBackoffInterval;
}
// Relational: maxTotalBackoffDuration >= 2x max(maxBackoffInterval, rateLimitConfig.maxRetryInterval)
const maxInterval = Math.max(
validated.maxBackoffInterval,
rateLimitConfig?.maxRetryInterval ?? 0
);
const minTotalDuration = maxInterval * 2;
if (validated.maxTotalBackoffDuration < minTotalDuration) {
logger?.warn(
`maxTotalBackoffDuration ${validated.maxTotalBackoffDuration}s clamped to ${minTotalDuration}s (2x max interval)`
);
validated.maxTotalBackoffDuration = minTotalDuration;
}
return validated;
};
/**
* Validates and normalizes integrations from CDN response.
* Returns null if integrations are malformed (triggers fallback to defaults).
*/
export const validateIntegrations = (
settings: SegmentAPISettings,
logger?: LoggerType
): SegmentAPIIntegrations | null => {
// A valid 200 with missing integrations means "no integrations configured"
if (settings.integrations == null) {
return {};
}
// Only fall back to defaults for truly malformed types (non-object or array)
if (
typeof settings.integrations !== 'object' ||
Array.isArray(settings.integrations)
) {
logger?.warn(
'CDN response has invalid integrations, falling back to defaults'
);
return null;
}
return settings.integrations;
};
/**
* Extracts, merges, and validates httpConfig from CDN response.
* Deep-merges with defaults and validates all values.
*/
export const extractHttpConfig = (
serverConfig: HttpConfig,
logger?: LoggerType
): HttpConfig => {
const mergedRateLimit = serverConfig.rateLimitConfig
? {
...defaultHttpConfig.rateLimitConfig!,
...serverConfig.rateLimitConfig,
}
: defaultHttpConfig.rateLimitConfig!;
const mergedBackoff = serverConfig.backoffConfig
? {
...defaultHttpConfig.backoffConfig!,
...serverConfig.backoffConfig,
statusCodeOverrides: {
...defaultHttpConfig.backoffConfig!.statusCodeOverrides,
...serverConfig.backoffConfig.statusCodeOverrides,
},
}
: defaultHttpConfig.backoffConfig!;
const validatedRateLimit = validateRateLimitConfig(mergedRateLimit, logger);
return {
rateLimitConfig: validatedRateLimit,
backoffConfig: validateBackoffConfig(
mergedBackoff,
logger,
validatedRateLimit
),
};
};
/**
* Merge httpConfig from 3 sources with priority: default < CDN < client overrides
* Used during settings initialization to combine default, CDN, and user configs.
*/
export const mergeHttpConfig = (
cdnConfig: HttpConfig | undefined,
clientConfig: DeepPartial<HttpConfig> | undefined,
logger?: LoggerType
): HttpConfig => {
const mergedRateLimit = {
...defaultHttpConfig.rateLimitConfig!,
...(cdnConfig?.rateLimitConfig ?? {}),
...(clientConfig?.rateLimitConfig ?? {}),
};
const mergedBackoff = {
...defaultHttpConfig.backoffConfig!,
...(cdnConfig?.backoffConfig ?? {}),
...(clientConfig?.backoffConfig ?? {}),
statusCodeOverrides: {
...defaultHttpConfig.backoffConfig!.statusCodeOverrides,
...(cdnConfig?.backoffConfig?.statusCodeOverrides ?? {}),
...(clientConfig?.backoffConfig?.statusCodeOverrides ?? {}),
},
};
const validatedRateLimit = validateRateLimitConfig(mergedRateLimit, logger);
return {
rateLimitConfig: validatedRateLimit,
backoffConfig: validateBackoffConfig(
mergedBackoff,
logger,
validatedRateLimit
),
};
};