diff --git a/.fernignore b/.fernignore index 084a8eb..be51463 100644 --- a/.fernignore +++ b/.fernignore @@ -1 +1,4 @@ # Specify files that shouldn't be modified by Fern +src/index.ts +src/area.ts +src/RegionalClient.ts diff --git a/src/RegionalClient.ts b/src/RegionalClient.ts new file mode 100644 index 0000000..4c78343 --- /dev/null +++ b/src/RegionalClient.ts @@ -0,0 +1,95 @@ +/** + * Regional Agora client with area-based URL selection. + * + * This module provides a wrapper client that extends the base AgoraClient + * with support for regional endpoint selection. + */ + +import { AgentsClient } from "./api/resources/agents/client/Client.js"; +import { PhoneNumbersClient } from "./api/resources/phoneNumbers/client/Client.js"; +import { TelephonyClient } from "./api/resources/telephony/client/Client.js"; +import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; +import { normalizeClientOptions } from "./BaseClient.js"; +import { type GatewayArea, RegionalEndpointPool } from "./area.js"; + +export declare namespace RegionalAgoraClient { + export interface Options extends BaseClientOptions { + /** + * The geographic area for endpoint selection. If provided, the client + * will automatically select the appropriate regional endpoint. + */ + gatewayArea?: GatewayArea; + /** + * A pre-configured endpoint pool. Use this if you want to manage + * the pool lifecycle yourself or share a pool across multiple clients. + */ + endpointPool?: RegionalEndpointPool; + } + + export interface RequestOptions extends BaseRequestOptions {} +} + +/** + * Agora client with regional endpoint selection. + * + * This client extends the base functionality with support for area-based + * URL selection, allowing you to connect to the optimal regional endpoint. + * + * @example + * ```typescript + * import { AgoraClient, GatewayArea } from "agora-sdk"; + * + * const client = new AgoraClient({ + * gatewayArea: GatewayArea.US, + * username: "YOUR_USERNAME", + * password: "YOUR_PASSWORD", + * }); + * ``` + */ +export class RegionalAgoraClient { + protected readonly _options: RegionalAgoraClient.Options; + protected readonly _endpointPool: RegionalEndpointPool | undefined; + protected _agents: AgentsClient | undefined; + protected _telephony: TelephonyClient | undefined; + protected _phoneNumbers: PhoneNumbersClient | undefined; + + constructor(options: RegionalAgoraClient.Options) { + const resolvedOptions = { ...options }; + let endpointPool: RegionalEndpointPool | undefined; + + if (options.baseUrl == null) { + if (options.endpointPool != null) { + endpointPool = options.endpointPool; + } else if (options.gatewayArea != null) { + endpointPool = new RegionalEndpointPool(options.gatewayArea); + } + + if (endpointPool != null) { + const pool = endpointPool; + resolvedOptions.baseUrl = () => pool.getCurrentUrl(); + } + } + + this._endpointPool = endpointPool; + this._options = normalizeClientOptions(resolvedOptions); + } + + /** + * The endpoint pool used by this client, if any. + */ + public get endpointPool(): RegionalEndpointPool | undefined { + return this._endpointPool; + } + + public get agents(): AgentsClient { + return (this._agents ??= new AgentsClient(this._options)); + } + + public get telephony(): TelephonyClient { + return (this._telephony ??= new TelephonyClient(this._options)); + } + + public get phoneNumbers(): PhoneNumbersClient { + return (this._phoneNumbers ??= new PhoneNumbersClient(this._options)); + } +} diff --git a/src/area.ts b/src/area.ts new file mode 100644 index 0000000..c69dc7f --- /dev/null +++ b/src/area.ts @@ -0,0 +1,122 @@ +/** + * Regional endpoint management for Agora API. + * + * This module provides area-based URL selection with automatic region cycling + * for optimal connectivity. + */ + +/** + * Global regions where the Open API gateway endpoint is located. + */ +export const GatewayArea = { + US: "US", + EU: "EU", + APAC: "APAC", + CN: "CN", +} as const; + +export type GatewayArea = (typeof GatewayArea)[keyof typeof GatewayArea]; + +const CHINESE_MAINLAND_DOMAIN = "sd-rtn.com"; +const OVERSEAS_DOMAIN = "agora.io"; + +const US_WEST_REGION_PREFIX = "api-us-west-1"; +const US_EAST_REGION_PREFIX = "api-us-east-1"; + +const AP_SOUTHEAST_REGION_PREFIX = "api-ap-southeast-1"; +const AP_NORTHEAST_REGION_PREFIX = "api-ap-northeast-1"; + +const EU_WEST_REGION_PREFIX = "api-eu-west-1"; +const EU_CENTRAL_REGION_PREFIX = "api-eu-central-1"; + +const CN_EAST_REGION_PREFIX = "api-cn-east-1"; +const CN_NORTH_REGION_PREFIX = "api-cn-north-1"; + +interface DomainConfig { + regionPrefixes: string[]; + domainSuffixes: string[]; +} + +const REGION_DOMAIN_CONFIG: Record = { + [GatewayArea.US]: { + regionPrefixes: [US_WEST_REGION_PREFIX, US_EAST_REGION_PREFIX], + domainSuffixes: [OVERSEAS_DOMAIN, CHINESE_MAINLAND_DOMAIN], + }, + [GatewayArea.EU]: { + regionPrefixes: [EU_WEST_REGION_PREFIX, EU_CENTRAL_REGION_PREFIX], + domainSuffixes: [OVERSEAS_DOMAIN, CHINESE_MAINLAND_DOMAIN], + }, + [GatewayArea.APAC]: { + regionPrefixes: [AP_SOUTHEAST_REGION_PREFIX, AP_NORTHEAST_REGION_PREFIX], + domainSuffixes: [OVERSEAS_DOMAIN, CHINESE_MAINLAND_DOMAIN], + }, + [GatewayArea.CN]: { + regionPrefixes: [CN_EAST_REGION_PREFIX, CN_NORTH_REGION_PREFIX], + domainSuffixes: [CHINESE_MAINLAND_DOMAIN, OVERSEAS_DOMAIN], + }, +}; + +/** + * Manages a pool of regional URLs with automatic cycling. + * + * This class provides: + * - Area-based endpoint selection (US, EU, APAC, CN) + * - Region cycling for failover scenarios + */ +export class RegionalEndpointPool { + private readonly _gatewayArea: GatewayArea; + private readonly _domainSuffixes: string[]; + private _currentDomain: string; + private readonly _regionPrefixes: string[]; + private _currentRegionPrefixes: string[]; + + /** + * Initialize a regional endpoint pool for the specified area. + * + * @param gatewayArea - The geographic area for endpoint selection. + * @throws Error if the gateway area is not valid. + */ + constructor(gatewayArea: GatewayArea) { + const config = REGION_DOMAIN_CONFIG[gatewayArea]; + if (!config) { + throw new Error(`Invalid gateway area: ${gatewayArea}`); + } + + this._gatewayArea = gatewayArea; + this._domainSuffixes = [...config.domainSuffixes]; + this._currentDomain = this._domainSuffixes[0]; + this._regionPrefixes = [...config.regionPrefixes]; + this._currentRegionPrefixes = [...this._regionPrefixes]; + } + + /** + * Cycle to the next region prefix in the pool. + * + * This method is useful for failover scenarios where the current + * region is not responding. + */ + public nextRegion(): void { + this._currentRegionPrefixes = this._currentRegionPrefixes.slice(1); + if (this._currentRegionPrefixes.length === 0) { + this._currentRegionPrefixes = [...this._regionPrefixes]; + } + } + + /** + * Get the current base URL based on the selected region and domain. + * + * @returns The full base URL for API requests. + */ + public getCurrentUrl(): string { + const currentRegion = this._currentRegionPrefixes[0]; + const currentDomain = this._currentDomain; + return `https://${currentRegion}.${currentDomain}/api/conversational-ai-agent`; + } + + /** + * The gateway area this pool is configured for. + */ + public get gatewayArea(): GatewayArea { + return this._gatewayArea; + } +} diff --git a/src/index.ts b/src/index.ts index a4a2376..039693e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * as Agora from "./api/index.js"; +export { GatewayArea, RegionalEndpointPool } from "./area.js"; export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; -export { AgoraClient } from "./Client.js"; +export { RegionalAgoraClient as AgoraClient } from "./RegionalClient.js"; export { AgoraEnvironment } from "./environments.js"; export { AgoraError, AgoraTimeoutError } from "./errors/index.js"; export * from "./exports.js";