Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Specify files that shouldn't be modified by Fern
src/index.ts
src/area.ts
src/RegionalClient.ts
95 changes: 95 additions & 0 deletions src/RegionalClient.ts
Original file line number Diff line number Diff line change
@@ -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));
}
}
122 changes: 122 additions & 0 deletions src/area.ts
Original file line number Diff line number Diff line change
@@ -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, DomainConfig> = {
[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;
}
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Loading