aluvia-ts-sdk
Version:
Official Aluvia proxy management SDK for Node.js and modern JavaScript environments
643 lines • 22.6 kB
JavaScript
import { api } from "./api-client.js";
import { validateApiToken, validateUsername, validateProxyCount, } from "./validation.js";
import { ApiError } from "./errors.js";
/**
* Represents a single proxy instance with methods for configuration and connection management.
*
* @example
* ```typescript
* const sdk = new Aluvia('your-api-token');
* const proxy = await sdk.first();
*
* // Laravel-style property setting
* proxy.useSticky = true;
* proxy.useSmartRouting = true;
* await proxy.save(); // Apply changes to server
*
* // Get the proxy URLs
* const httpUrl = proxy.toUrl('http'); // http://username:password@proxy.aluvia.io:8080
* const httpsUrl = proxy.toUrl('https'); // https://username:password@proxy.aluvia.io:8443
* ```
*
* @public
*/
export class Proxy {
/**
* Creates a new Proxy instance.
*
* @param credential - The proxy authentication credentials
* @param config - The proxy server configuration
* @param sdk - Reference to the parent Aluvia SDK instance
* @internal
*/
constructor(credential, config, sdk) {
this.credential = credential;
this.config = config;
this.sdk = sdk;
}
/**
* Retrieves detailed usage information for this proxy.
*
* @param options - Optional date range filtering
* @returns A promise that resolves to detailed usage information
* @throws {Error} When the API request fails
*
* @example
* ```typescript
* const proxy = await sdk.first();
* const usage = await proxy.getUsage();
* console.log(`This proxy has used ${usage.dataUsed} GB`);
*
* // Get usage for last week
* const lastWeek = await proxy.getUsage({
* usageStart: Math.floor(Date.now() / 1000) - (7 * 24 * 60 * 60),
* usageEnd: Math.floor(Date.now() / 1000)
* });
* ```
*/
async getUsage(options) {
return this.sdk.getUsage(this.credential.username, options);
}
/**
* Saves any changes made to the proxy configuration to the server.
*
* This method syncs the current proxy state (sticky sessions, smart routing)
* with the Aluvia API server. Call this after modifying any properties.
*
* @returns A promise that resolves to the updated Proxy instance
* @throws {ApiError} When the update fails or the proxy doesn't exist
* @throws {NetworkError} When network connectivity issues occur
*
* @example
* ```typescript
* const proxy = await sdk.first();
* proxy.useSticky = true;
* proxy.useSmartRouting = true;
* await proxy.save(); // Sync with server
* ```
*/
async save() {
// Generate new session salt every time sticky is enabled
if (this.credential.useSticky) {
this.credential.sessionSalt = this.generateSessionSalt();
}
// Clear session salt when disabling sticky sessions
if (!this.credential.useSticky) {
this.credential.sessionSalt = undefined;
}
await this.sdk.update(this.credential.username, {
useSticky: this.credential.useSticky,
useSmartRouting: this.credential.useSmartRouting,
});
return this;
}
/**
* Gets or sets whether sticky sessions are enabled for this proxy.
*
* Sticky sessions ensure that subsequent requests from the same client
* are routed through the same exit IP address for session consistency.
*
* @example
* ```typescript
* const proxy = await sdk.first();
* proxy.useSticky = true;
* await proxy.save(); // Apply changes
* ```
*/
get useSticky() {
return this.credential.useSticky || false;
}
set useSticky(enabled) {
this.credential.useSticky = enabled;
}
/**
* Gets or sets whether smart routing is enabled for this proxy.
*
* Smart routing automatically selects the optimal path based on
* network conditions and target destination for improved performance.
*
* @example
* ```typescript
* const proxy = await sdk.first();
* proxy.useSmartRouting = true;
* await proxy.save(); // Apply changes
* ```
*/
get useSmartRouting() {
return this.credential.useSmartRouting || false;
}
set useSmartRouting(enabled) {
this.credential.useSmartRouting = enabled;
}
/**
* Gets the current username with all enabled features encoded.
*
* @returns The formatted username including session salts and routing suffixes
*
* @example
* ```typescript
* const proxy = await sdk.first();
* console.log(proxy.username); // 'user123-session-abc123-routing-smart'
* ```
*/
get username() {
return this.buildCredential().username;
}
/**
* Gets the proxy password for authentication.
*
* @returns The proxy authentication password
*/
get password() {
return this.credential.password;
}
/**
* Gets the proxy server hostname.
*
* @returns The hostname or IP address of the proxy server
*/
get host() {
return this.config.host;
}
/**
* Gets the HTTP port number for the proxy server.
*
* @returns The HTTP port number (typically 8080)
*/
get httpPort() {
return this.config.httpPort;
}
/**
* Gets the HTTPS port number for the proxy server.
*
* @returns The HTTPS port number (typically 8443)
*/
get httpsPort() {
return this.config.httpsPort;
}
/**
* Generates a formatted proxy URL for connecting through this proxy.
*
* The URL includes all enabled features (sticky sessions, smart routing)
* encoded in the username format.
*
* @param protocol - The protocol to use in the URL (default: 'http')
* @returns A fully formatted proxy URL ready for use
*
* @example
* ```typescript
* const proxy = await sdk.first();
* const httpUrl = proxy.toUrl('http');
* const httpsUrl = proxy.toUrl('https');
*
* // Default is http
* const defaultUrl = proxy.toUrl();
* ```
*/
toUrl(protocol = "http") {
const builtCredential = this.buildCredential();
const port = protocol === "https" ? this.config.httpsPort : this.config.httpPort;
return `${protocol}://${builtCredential.username}:${builtCredential.password}@${this.config.host}:${port}`;
}
/**
* Permanently deletes this proxy from your Aluvia account.
*
* @returns A promise that resolves when deletion is complete
* @throws {Error} When the deletion fails or the proxy doesn't exist
*
* @example
* ```typescript
* const proxy = await sdk.first();
* await proxy.delete();
* console.log('Proxy deleted successfully');
* ```
*/
async delete() {
return this.sdk.delete(this.credential.username);
}
/**
* Converts the proxy instance to a JSON-serializable object.
*
* This method is automatically called by JSON.stringify() and provides
* a clean representation of the proxy for serialization, logging, and debugging.
*
* @returns A plain object containing all proxy properties
*
* @example
* ```typescript
* const proxy = await sdk.first();
*
* // Automatic JSON serialization
* const jsonString = JSON.stringify(proxy);
* console.log(jsonString);
*
* // Manual conversion
* const obj = proxy.toJSON();
* console.log(obj);
*
* // Spreading into other objects
* const config = { ...proxy.toJSON(), timeout: 5000 };
* ```
*/
toJSON() {
return {
username: this.username,
password: this.password,
host: this.host,
httpPort: this.httpPort,
httpsPort: this.httpsPort,
useSticky: this.useSticky,
useSmartRouting: this.useSmartRouting,
};
}
/**
* Build credential with proper username formatting
*/
buildCredential() {
let username = stripUsernameSuffixes(this.credential.username);
// Add sticky session suffix
if (this.credential.useSticky && this.credential.sessionSalt) {
username += `-session-${this.credential.sessionSalt}`;
}
// Add smart routing suffix
if (this.credential.useSmartRouting) {
username += "-routing-smart";
}
return {
...this.credential,
username,
};
}
/**
* Generate random session salt
*/
generateSessionSalt(length = 8) {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
}
/**
* The main Aluvia SDK client for managing proxy connections.
*
* This class provides methods to create, retrieve, and manage proxy instances
* through the Aluvia API. All operations require a valid API token.
*
* @example
* ```typescript
* import Aluvia from 'aluvia-ts-sdk';
*
* const sdk = new Aluvia('your-api-token');
*
* // Get your first available proxy
* const proxy = await sdk.first();
*
* // Create new proxies
* const newProxies = await sdk.create(3);
*
* // Find a specific proxy
* const foundProxy = await sdk.find('username123');
*
* // Update proxy settings
* await sdk.update('username123', { useSticky: true });
*
* // Get usage information
* const usage = await sdk.getUsage('username123');
* ```
*
* @public
*/
export class Aluvia {
/**
* Creates a new Aluvia SDK instance.
*
* @param token - Your Aluvia API authentication token
* @throws {ValidationError} When token is not provided, invalid, or empty
*
* @example
* ```typescript
* const sdk = new Aluvia('alv_abc123...');
* ```
*/
constructor(token) {
this.config = {
host: "proxy.aluvia.io",
httpPort: 8080,
httpsPort: 8443,
};
this.credentials = [];
this.token = validateApiToken(token);
}
parseOptions(options) {
return {
useSticky: options && "use_sticky" in options
? options.use_sticky || false
: false,
useSmartRouting: options && "use_smart_routing" in options
? options.use_smart_routing || false
: false,
};
}
/**
* Retrieves the most recently created proxy from your account.
*
* This is typically the fastest way to get a working proxy connection
* when you don't need a specific proxy instance.
*
* @returns A promise that resolves to a Proxy instance, or null if no proxies exist
* @throws {ApiError} When the API request fails or returns an error response
* @throws {ValidationError} When the API token is invalid
* @throws {NetworkError} When network connectivity issues occur
*
* @example
* ```typescript
* const sdk = new Aluvia('your-token');
* const proxy = await sdk.first();
*
* if (proxy) {
* console.log('Proxy URL:', proxy.toUrl());
* } else {
* console.log('No proxies available, create one first');
* }
* ```
*/
async first() {
await this.initCredentials();
if (this.credentials.at(0)) {
return new Proxy(this.credentials[0], this.config, this);
}
else {
return null;
}
}
async initCredentials() {
if (this.credentials.length) {
return this.credentials;
}
const headers = { Authorization: `Bearer ${this.token}` };
const response = await api.get("/credentials", headers);
if (!response.success) {
throw new ApiError(response.message || "Failed to load credentials");
}
this.credentials = response.data?.map((cred) => ({
username: cred.username,
password: cred.password,
...this.parseOptions(cred.options),
}));
}
/**
* Finds and returns a specific proxy by its username.
*
* The method automatically handles username variations by stripping
* session and routing suffixes before performing the lookup.
*
* @param username - The base username of the proxy to find
* @returns A promise that resolves to a Proxy instance, or null if not found
* @throws {ApiError} When the API request fails (excluding 404 not found)
* @throws {ValidationError} When the username format is invalid
* @throws {NetworkError} When network connectivity issues occur
*
* @example
* ```typescript
* const sdk = new Aluvia('your-token');
*
* // These all find the same proxy:
* const proxy1 = await sdk.find('user123');
* const proxy2 = await sdk.find('user123-session-abc');
* const proxy3 = await sdk.find('user123-routing-smart');
* ```
*/
async find(username) {
try {
const baseUsername = stripUsernameSuffixes(username);
const match = this.credentials.find((cred) => stripUsernameSuffixes(cred.username) === baseUsername);
if (match) {
return new Proxy(match, this.config, this);
}
const headers = { Authorization: `Bearer ${this.token}` };
const response = await api.get("/credentials/" + baseUsername, headers);
if (!response.success) {
return null;
}
const credential = {
username: response.data.username,
password: response.data.password,
...this.parseOptions(response.data.options),
};
this.credentials.push(credential);
return new Proxy(credential, this.config, this);
}
catch (error) {
if (error instanceof ApiError && error.statusCode === 404) {
return null; // Proxy not found is not an error condition
}
throw error;
}
}
/**
* Creates new proxy instances in your Aluvia account.
*
* @param count - The number of proxies to create (default: 1, max varies by plan)
* @returns A promise that resolves to an array of newly created Proxy instances
* @throws {ApiError} When creation fails or quota is exceeded
* @throws {ValidationError} When the count parameter is invalid (< 1 or > limit)
* @throws {NetworkError} When network connectivity issues occur
*
* @example
* ```typescript
* const sdk = new Aluvia('your-token');
*
* // Create a single proxy
* const [proxy] = await sdk.create(1);
*
* // Create multiple proxies
* const proxies = await sdk.create(5);
* console.log(`Created ${proxies.length} new proxies`);
* ```
*/
async create(count = 1) {
const validCount = validateProxyCount(count);
const headers = { Authorization: `Bearer ${this.token}` };
const response = await api.post("/credentials", { count: validCount }, headers);
if (!response.success) {
const errorMsg = response.message || "Failed to create proxies";
throw new ApiError(`${errorMsg}. Requested count: ${validCount}`);
}
const newCredentials = response.data.map((cred) => ({
username: cred.username,
password: cred.password,
...this.parseOptions(cred.options),
}));
this.credentials.push(...newCredentials);
return newCredentials.map((cred) => new Proxy(cred, this.config, this));
}
/**
* Updates a specific proxy's configuration on the server.
*
* This method allows you to update proxy settings (sticky sessions, smart routing)
* for a specific proxy by username, similar to how find() and delete() work.
*
* @param username - The username of the proxy to update
* @param options - The settings to update
* @returns A promise that resolves to the updated Proxy instance, or null if not found
* @throws {ApiError} When the update fails or the proxy doesn't exist
* @throws {ValidationError} When the username or options are invalid
* @throws {NetworkError} When network connectivity issues occur
*
* @example
* ```typescript
* const sdk = new Aluvia('your-token');
*
* // Update specific proxy settings
* const updatedProxy = await sdk.update('user123', {
* useSticky: true,
* useSmartRouting: true
* });
* ```
*/
async update(username, options) {
const baseUsername = stripUsernameSuffixes(username);
const headers = { Authorization: `Bearer ${this.token}` };
const updateData = {
options: {
use_sticky: options.useSticky,
use_smart_routing: options.useSmartRouting,
},
};
const response = await api.patch(`/credentials/${baseUsername}`, updateData, headers);
if (!response.success) {
const errorMsg = response.message || "Failed to update proxy";
throw new ApiError(`${errorMsg}. Username: ${baseUsername}`);
}
this.credentials = this.credentials.map((cred) => {
if (stripUsernameSuffixes(cred.username) !== baseUsername) {
return cred;
}
return {
...cred,
...this.parseOptions(response.data.options),
};
});
return this.find(baseUsername);
}
/**
* Permanently deletes a proxy from your account by username.
*
* This action cannot be undone. The proxy will be immediately unavailable
* for new connections and removed from your account.
*
* @param username - The username of the proxy to delete
* @returns A promise that resolves when deletion is complete
* @throws {ApiError} When deletion fails or the proxy doesn't exist
* @throws {ValidationError} When the username format is invalid
* @throws {NetworkError} When network connectivity issues occur
*
* @example
* ```typescript
* const sdk = new Aluvia('your-token');
* await sdk.delete('user123');
* console.log('Proxy deleted successfully');
* ```
*/
async delete(username) {
const validUsername = validateUsername(username);
const baseUsername = stripUsernameSuffixes(validUsername);
const headers = { Authorization: `Bearer ${this.token}` };
const response = await api.delete("/credentials/" + baseUsername, headers);
if (!response.success) {
throw new ApiError(response.message || "Failed to delete proxy");
}
this.credentials = this.credentials.filter((cred) => {
return stripUsernameSuffixes(cred.username) !== baseUsername;
});
}
/**
* Returns all currently loaded proxy instances.
*
* Note: This returns proxies from the local cache that have been loaded
* through other method calls (first, find, create). To get all proxies
* from your account, you'll need to implement pagination through the API.
*
* @returns An array of all currently loaded Proxy instances
*
* @example
* ```typescript
* const sdk = new Aluvia('your-token');
*
* await sdk.create(3);
* const allProxies = sdk.all();
* console.log(`Local cache contains ${allProxies.length} proxies`);
* ```
*/
async all() {
await this.initCredentials();
return this.credentials.map((cred) => new Proxy(cred, this.config, this));
}
/**
* Retrieves detailed usage information for a specific proxy.
*
* This method provides comprehensive usage statistics including data consumption
* over a specified time period or the default period if no dates are provided.
*
* @param username - The username of the proxy to get usage for
* @param options - Optional date range filtering
* @returns A promise that resolves to detailed usage information
* @throws {ApiError} When the API request fails or the proxy doesn't exist
* @throws {ValidationError} When the username or date range is invalid
* @throws {NetworkError} When network connectivity issues occur
*
* @example
* ```typescript
* const sdk = new Aluvia('your-token');
*
* // Get usage for current period
* const usage = await sdk.getUsage('user123');
* console.log(`Data used: ${usage.dataUsed} GB`);
*
* // Get usage for specific date range
* const customUsage = await sdk.getUsage('user123', {
* usageStart: 1705478400,
* usageEnd: 1706083200
* });
* ```
*/
async getUsage(username, options) {
const validUsername = validateUsername(username);
const baseUsername = stripUsernameSuffixes(validUsername);
const headers = { Authorization: `Bearer ${this.token}` };
const queryParams = new URLSearchParams();
if (options?.usageStart) {
queryParams.append("usage_start", options.usageStart.toString());
}
if (options?.usageEnd) {
queryParams.append("usage_end", options.usageEnd.toString());
}
const endpoint = `/credentials/${baseUsername}${queryParams.toString() ? "?" + queryParams.toString() : ""}`;
const response = await api.get(endpoint, headers);
if (response.success) {
return {
usageStart: response.data.usage_start,
usageEnd: response.data.usage_end,
dataUsed: response.data.data_used,
};
}
throw new ApiError(response.message || "Failed to get proxy usage");
}
}
/** SDK version for tracking and debugging */
Aluvia.VERSION = "1.0.0";
/**
* Strip session and routing suffixes from username
*/
function stripUsernameSuffixes(username) {
return username
.replace(/-session-[a-zA-Z0-9]+/, "")
.replace(/-routing-smart/, "");
}
// Export error types for users
export { AluviaError, AuthenticationError, NetworkError, ApiError, ValidationError, NotFoundError, RateLimitError, } from "./errors.js";
export default Aluvia;
//# sourceMappingURL=index.js.map